-- Master Mounts self-test: /mm selftest
--
-- Not a unit-test suite. Mocking WoW well enough to unit-test this addon would
-- cost more than the addon and still not answer the question that actually
-- matters before release, which is: *is anything silently switched off in this
-- client?*
--
-- 41 of our 58 external API calls are deliberately guarded so a missing one
-- degrades instead of erroring. That is right for players and dangerous for us:
-- Callings detection, bag scanning and quest-zone resolution can all be dead on
-- arrival and the only symptom is a slightly emptier plan. This turns that
-- silence into a report.
--
-- Three sections:
--   API    -- does this client actually expose what each feature needs
--   DATA   -- invariants over the shipped database
--   LOGIC  -- pure functions with known inputs and known answers
--
-- Everything here is read-only. It does not modify the plan, the route or any
-- saved variable.
local _, MM = ...
local U = MM.Util

MM.Tests = {}
local T = MM.Tests

local results, current
local PASS, FAIL, WARN = "PASS", "FAIL", "WARN"

local function record(state, group, name, detail)
	results[#results + 1] = { state = state, group = group, name = name, detail = detail }
end

-- COLLECT MODE, so the whole suite can be run a few checks at a time.
--
-- 193 checks in one execution is 2.6 seconds on a fast machine, and the
-- client's watchdog measures a single uninterrupted run -- which is how
-- `/mm report` died with "script ran too long" on slower hardware while
-- looking healthy here. Chunking the report between SECTIONS did not help,
-- because one section is the whole problem.
--
-- The checks themselves are unchanged and unaware. In collect mode `check`
-- files the closure instead of calling it, so the three run* functions build a
-- queue for the price of a few hundred table inserts and something else
-- decides when each one actually runs.
local queue, collecting = {}, false

-- HOW LONG EACH CHECK TOOK, kept.
--
-- Timing the report by SECTION found that one section was the whole problem.
-- The same question one level down went unanswered: a check that runs too long
-- on slower hardware throws, and the throw names whatever line the axe fell on
-- rather than the check. So the check names itself now.
T.checkMs = T.checkMs or {}

local function runOne(item)
	local wasCurrent = current
	current = item.group
	local at = debugprofilestop and debugprofilestop()
	local ran, ok, detail = pcall(item.fn)
	if at then T.checkMs[item.name] = debugprofilestop() - at end
	if not ran then
		record(FAIL, current, item.name, "threw: " .. tostring(ok))
	else
		record(ok == true and PASS or (ok == nil and WARN or FAIL),
			current, item.name, detail)
	end
	current = wasCurrent
end

-- A route to work with, without paying for one that already exists.
--
-- BuildSync does the whole job in one call -- roughly 1.3 seconds on an 82-stop
-- plan, against 35 ms for the chunked build that reports itself. Several checks
-- opened by forcing one purely to have a route in hand, and by then the suite
-- had already built one. That single redundant call was most of the cost of the
-- two slowest checks in the suite.
--
-- Anything testing BUILD BEHAVIOUR still builds; this only skips fetching what
-- is already there.
-- The checks that cannot avoid a FULL SYNCHRONOUS BUILD.
--
-- BuildSync does the whole route in one call -- about three seconds on a
-- 90-stop plan, against the 37 ms the chunked build reports for the same work.
-- Two checks exist precisely to verify build behaviour and cannot do it
-- without one, so no amount of trimming brings them under a per-check budget:
-- the budget is the wrong instrument for them, not a bar they are failing.
--
-- Named and reported rather than quietly skipped, because an exemption nobody
-- can see is exactly how a real regression hides.
--
-- THE THIRD ONE PAYS FOR IT WITHOUT CALLING IT, which is why it took four builds
-- to spot. The plan check calls Planner:Add and Planner:Remove -- a table insert
-- and a table remove -- but each fires MM_PLAN_CHANGED, and Router's handler runs
-- BuildSync whenever a route is active. Two edits, two full builds, ~3.6 seconds,
-- none of it visible at the call site.
--
-- Measured rather than reasoned, eventually: the per-handler profiler put the
-- whole cost in Router, and a three-way split inside that handler put 1653 ms of
-- it in the build with nothing in the waypoint or the nested fire.
--
-- And the 56 ms that "Building the route is fast" reports for the same function
-- is not a contradiction -- it is a different path. RunBuild restores chartRank
-- from the stored chart only when the stored signature still matches, and reading
-- that chart is what skips the O(n^2) travel scan. A plan EDIT changes the
-- signature, so the chart is stale and the scan runs; that check never edits the
-- plan, so it has only ever timed the cached path. Re-charting after a plan
-- change is the designed cost, not a regression.
local BUILD_BOUND = {
	["A swapped route is not mistaken for a cache hit"] = true,
	["The router model gives the route back"] = true,
	["Adding to the plan from any pane actually adds"] = true,
	-- Changes the order cap and re-charts to measure what that costs. A weight
	-- change moves the signature, so the stored chart cannot be restored and
	-- every one of those builds is the full search. The time is the ROUTER's,
	-- which is what this list is for -- and it is reported separately by the
	-- route section rather than hidden.
	["The cap costs travel, and says how much"] = true,
	-- Drives two synchronous builds to prove an unanchored rebuild leads from
	-- the top. It used to ask for a chunked build and read the index on the
	-- next line, which was quick and wrong; waiting for the build is what makes
	-- it correct, and the wait is the router's cost.
	["The arrow and the plan cannot disagree about where you are going"] = true,
	-- Opens with an unconditional BuildSync, and has to: it then asks whether
	-- trimming to the CURRENT plan removes anything, which is only a fair
	-- question if the route was built from that same plan. An earlier check
	-- that edits the plan leaves the signature stale, so this build is the full
	-- search rather than a restore -- the case the note above describes.
	--
	-- `routeInHand` was the tempting fix and is the wrong one: reusing a route
	-- built before another check moved the plan would make DropUnplanned remove
	-- goals legitimately, and this check would fail for a reason that is not
	-- its subject. The build is load-bearing, so it is charged to the router.
	["A plan edit does not re-chart on the frame"] = true,
	-- Opens with an unconditional BuildSync for the same reason as the one
	-- above: it asks whether Build reports a SETTLED route, which is only a
	-- fair question if the route was just built.
	--
	-- It sat outside this list for a long time and passed, which was luck
	-- rather than speed. An earlier check happened to leave the chart warm, so
	-- its BuildSync was a restore rather than a search. When the signature
	-- started moving -- learned portals joining the graph -- the full search
	-- landed here instead, and 3.9 seconds appeared in a check that had never
	-- looked expensive. The cost was always the router's; only which check
	-- paid it moved.
	["Build answers with a status, never a stop count"] = true,
	-- THREE SYNCHRONOUS BUILDS, none of them optional. Starting a session
	-- rebuilds the route, stopping it rebuilds again, and the BuildSync at the
	-- end restores what the next check inherits -- the check asserts a promise
	-- ABOUT the route, so it has to make the route it is asserting about, twice
	-- over, and then put the previous one back.
	--
	-- It passed at 1,146 ms for a long time and that was the same luck the note
	-- above describes: the plan grew by a dozen goals, the leading stops grew
	-- with it, and 1,247 ms landed in a check whose cost had never been its
	-- own. Its sibling, "A session promise is kept", is NOT here -- that one
	-- answers four questions with four fits and stays well inside the bar.
	["A session's promise reaches the route"] = true,
}

-- ONE ANSWER TO "WHICH CHECK IS SLOWEST", because there were two and they
-- disagreed. The self-test excluded the build-bound checks; the FIXES section in
-- Diagnostics computed its own worst case without them and hardcoded the count,
-- so the same run reported a clean self-test and "1 of 23 need looking at",
-- naming an exempt check as the offender. Two copies of one rule, again.
--
-- Returns nil when nothing has been timed, so callers can say so rather than
-- reporting a zero as a result.
function T.SlowestCheck()
	local ms = T.checkMs
	if not (ms and next(ms)) then return nil end
	local worst, worstMs, counted, exempt = nil, 0, 0, {}
	for name, t in pairs(ms) do
		if BUILD_BOUND[name] then
			exempt[#exempt + 1] = ("%s %d ms"):format(name, t)
		else
			counted = counted + 1
			if t > worstMs then worst, worstMs = name, t end
		end
	end
	table.sort(exempt)
	return worst, worstMs, counted, exempt
end

local function routeInHand(R, least)
	if #(R.route or {}) >= (least or 3) then return #R.route end
	R:BuildSync()
	return #(R.route or {})
end

-- MM_ROUTE_BUILT, counted, so a check can assert a build announced itself
-- exactly once -- and that an obsolete build announced itself not at all.
--
-- Hooked once and lazily. MM:On has no unsubscribe, so registering inside a
-- check would leave one more listener behind on every run.
local routeBuiltCount, routeBuiltHooked = 0, false
local function watchRouteBuilt()
	if not routeBuiltHooked then
		routeBuiltHooked = true
		MM:On("MM_ROUTE_BUILT", function() routeBuiltCount = routeBuiltCount + 1 end)
	end
	return routeBuiltCount
end

-- ok == true -> PASS, false -> FAIL, nil -> WARN (absent but survivable)
local function check(name, fn)
	if collecting then
		queue[#queue + 1] = { name = name, fn = fn, group = current }
		return
	end
	runOne({ name = name, fn = fn, group = current })
end

------------------------------------------------------------
-- API probes
------------------------------------------------------------
-- Each entry names the FEATURE that dies without it, because "C_Foo.Bar
-- missing" tells you nothing on its own.
local API_PROBES = {
	{ "Mount journal", function() return C_MountJournal and C_MountJournal.GetMountIDs ~= nil end, true },
	{ "Bag detection (items that teach mounts)",
		function() return C_MountJournal and C_MountJournal.GetMountFromItem ~= nil end },
	{ "Covenant Callings (Necroray rotation)",
		function() return C_CovenantCallings and C_CovenantCallings.RequestCallings ~= nil end },
	{ "Calling identification",
		function() return C_QuestLog and C_QuestLog.IsQuestCalling ~= nil end },
	{ "Covenant lookup",
		function() return C_Covenants and C_Covenants.GetActiveCovenantID ~= nil end },
	{ "Quest zone resolution", function()
		local n = 0
		if C_TaskQuest and C_TaskQuest.GetQuestZoneID then n = n + 1 end
		if C_QuestLog and C_QuestLog.GetQuestAdditionalHighlights then n = n + 1 end
		if GetQuestUiMapID then n = n + 1 end
		if n == 0 then return false, "no probe available — Calling zones unresolvable" end
		return true, ("%d of 3 probes available"):format(n)
	end },
	{ "Quest state (prerequisite gating)",
		function() return C_QuestLog and C_QuestLog.IsOnQuest ~= nil
			and C_QuestLog.GetTitleForQuestID ~= nil end },
	{ "Profession gating",
		function() return GetProfessions ~= nil and GetProfessionInfo ~= nil end },
	{ "Toy detection (teleports)", function() return PlayerHasToy ~= nil end },
	{ "Hearthstone teleport", function()
		-- Must test what Teleports actually needs, not merely that the API
		-- answers. It requires the bind NAME to resolve to a map; a player bound
		-- to an inn subzone ("Snug Harbor Inn") gets a perfectly good string
		-- here and no hearthstone option in practice. The first version of this
		-- probe passed on the string alone and would have reported a working
		-- feature that does nothing.
		local b = GetBindLocation and GetBindLocation()
		if not b or b == "" then return nil, "no bind location reported" end
		local learned = MM.db.hearthMaps and MM.db.hearthMaps[b]
		if learned then return true, ("%s (learned, map %d)"):format(b, learned) end
		if not (U and U.ResolveMapByName and U.ResolveMapByName(b)) then
			return nil, ("bound to %q — visit it once and we'll learn the map"):format(b)
		end
		return true, b
	end },
	{ "Close-button art", function()
		if not (C_Texture and C_Texture.GetAtlasInfo) then return nil, "atlas API absent — text glyph fallback" end
		if C_Texture.GetAtlasInfo("uitools-icon-close") == nil then
			return nil, "atlas missing — text glyph fallback in use"
		end
		return true
	end },
	{ "Instance lockouts",
		function() return GetSavedInstanceEncounterInfo ~= nil end },
	{ "Per-encounter lockouts",
		function() return C_RaidLocks and C_RaidLocks.IsEncounterComplete ~= nil end },
	{ "Trading Post",
		function() return C_PerksProgram and C_PerksProgram.GetAvailableVendorItemIDs ~= nil end },
	{ "Rare alerts (vignettes)",
		function() return C_VignetteInfo and C_VignetteInfo.GetVignettes ~= nil end },
	{ "Group compare (addon comms)",
		function() return C_ChatInfo and C_ChatInfo.RegisterAddonMessagePrefix ~= nil end },
	{ "Encounter Journal (ID resolution)",
		function() return EJ_SelectInstance ~= nil and EJ_GetEncounterInfoByIndex ~= nil end },
	{ "Calendar (holidays, Timewalking)",
		function() return C_Calendar and C_Calendar.GetNumDayEvents ~= nil end },
	{ "Rarity tuning (MountsRarity)", function()
		if not (LibStub and LibStub("MountsRarity-2.0", true)) then
			return nil, "not installed — difficulty uses our effort estimates only"
		end
		local pct = MM.Rarity.Get(6) -- a common mount; any number proves the table loaded
		if type(pct) ~= "number" then return false, "library present but returned no data" end
		return true, ("live, sample reads %.2f%%"):format(pct)
	end },
	{ "Currency", function() return C_CurrencyInfo and C_CurrencyInfo.GetCurrencyInfo ~= nil end, true },
	{ "Map positions", function() return C_Map and C_Map.GetPlayerMapPosition ~= nil end, true },
}

local function runAPI()
	current = "API"
	for _, probe in ipairs(API_PROBES) do
		local name, fn, required = probe[1], probe[2], probe[3]
		check(name, function()
			local ok, detail = fn()
			-- an optional feature that is absent is a WARN, not a failure:
			-- the addon is built to run without it
			if ok == false and not required then return nil, detail or "not available in this client" end
			return ok, detail
		end)
	end
end

------------------------------------------------------------
-- Data invariants
------------------------------------------------------------
local VALID_CATEGORY = {
	ACHIEVEMENT = true, CLASS = true, CURRENCY = true, DROP = true, GARRISON = true,
	HOLIDAY = true, PROFESSION = true, PROMOTION = true, PUZZLE = true, PVP = true,
	QUEST = true, RARE = true, REMOVED = true, REP = true, STORE = true, TCG = true,
	TIMEWALKING = true, TRADINGPOST = true, TREASURE = true, VENDOR = true,
	ZONEDROP = true,
}

local function canonicalRecords()
	local out = {}
	for _, rec in ipairs(MM.DBList or {}) do
		local canon = (rec.spellID and MM.DBBySpell[rec.spellID] == rec)
			or (rec.name and MM.DBByName[rec.name:lower()] == rec)
		if canon then out[#out + 1] = rec end
	end
	return out
end

local function runData()
	current = "DATA"
	local recs = canonicalRecords()

	check("Database loaded", function()
		if #recs == 0 then return false, "no records — Data/Mounts.lua did not load" end
		return true, ("%d canonical records"):format(#recs)
	end)

	check("Resolved IDs loaded", function()
		local ids = MM.ResolvedIDs
		local maps = 0
		for _ in pairs(ids and ids.maps or {}) do maps = maps + 1 end
		if maps == 0 then
			-- this exact failure shipped once: Data_87 called AddResolvedIDs
			-- before it existed, and every ID was discarded in silence
			return false, "no resolved maps — check Schema defines AddResolvedIDs"
		end
		return true, ("%d maps"):format(maps)
	end)

	check("Every record has a name", function()
		-- An empty table satisfies "nothing is wrong". Every check here
		-- counts defects and returns count == 0, which is TRUE for zero
		-- records -- so a database that failed to load would report a
		-- clean bill of health from all of them at once. Report nothing
		-- rather than something reassuring.
		if #recs == 0 then return nil, "no records loaded" end
		local bad = 0
		for _, r in ipairs(recs) do if not r.name or r.name == "" then bad = bad + 1 end end
		return bad == 0, bad > 0 and (bad .. " unnamed") or nil
	end)

	check("No duplicate spellIDs", function()
		-- An empty table satisfies "nothing is wrong". Every check here
		-- counts defects and returns count == 0, which is TRUE for zero
		-- records -- so a database that failed to load would report a
		-- clean bill of health from all of them at once. Report nothing
		-- rather than something reassuring.
		if #recs == 0 then return nil, "no records loaded" end
		local seen, dupes = {}, 0
		for _, r in ipairs(recs) do
			if r.spellID then
				if seen[r.spellID] then dupes = dupes + 1 else seen[r.spellID] = true end
			end
		end
		return dupes == 0, dupes > 0 and (dupes .. " duplicated") or nil
	end)

	check("Categories are known", function()
		-- An empty table satisfies "nothing is wrong". Every check here
		-- counts defects and returns count == 0, which is TRUE for zero
		-- records -- so a database that failed to load would report a
		-- clean bill of health from all of them at once. Report nothing
		-- rather than something reassuring.
		if #recs == 0 then return nil, "no records loaded" end
		local bad, example = 0, nil
		for _, r in ipairs(recs) do
			if r.category and not VALID_CATEGORY[r.category] then
				bad = bad + 1; example = example or (r.name .. " = " .. r.category)
			end
		end
		return bad == 0, example
	end)

	check("Coordinates in range", function()
		-- An empty table satisfies "nothing is wrong". Every check here
		-- counts defects and returns count == 0, which is TRUE for zero
		-- records -- so a database that failed to load would report a
		-- clean bill of health from all of them at once. Report nothing
		-- rather than something reassuring.
		if #recs == 0 then return nil, "no records loaded" end
		local bad, example = 0, nil
		for _, r in ipairs(recs) do
			local z = r.zone
			if z and (z.x or z.y) then
				if type(z.x) ~= "number" or type(z.y) ~= "number"
					or z.x < 0 or z.x > 100 or z.y < 0 or z.y > 100 then
					bad = bad + 1; example = example or r.name
				end
			end
		end
		return bad == 0, example
	end)

	check("Drop rates are percentages", function()
		-- An empty table satisfies "nothing is wrong". Every check here
		-- counts defects and returns count == 0, which is TRUE for zero
		-- records -- so a database that failed to load would report a
		-- clean bill of health from all of them at once. Report nothing
		-- rather than something reassuring.
		if #recs == 0 then return nil, "no records loaded" end
		local bad, example = 0, nil
		for _, r in ipairs(recs) do
			if r.dropRate and (type(r.dropRate) ~= "number"
				or r.dropRate <= 0 or r.dropRate > 100) then
				bad = bad + 1; example = example or (r.name .. " = " .. tostring(r.dropRate))
			end
		end
		return bad == 0, example
	end)

	check("Conditions well-formed", function()
		-- An empty table satisfies "nothing is wrong". Every check here
		-- counts defects and returns count == 0, which is TRUE for zero
		-- records -- so a database that failed to load would report a
		-- clean bill of health from all of them at once. Report nothing
		-- rather than something reassuring.
		if #recs == 0 then return nil, "no records loaded" end
		local bad, example = 0, nil
		for _, r in ipairs(recs) do
			for _, c in ipairs(r.conditions or {}) do
				if type(c) ~= "table" or type(c.type) ~= "string" then
					bad = bad + 1; example = example or r.name
				end
			end
		end
		return bad == 0, example
	end)

	check("A mount we already priced is not asked about again", function()
		-- The unpriced list asks a player to stand at a vendor and read a price
		-- off the screen. That is the scarcest thing this addon can request, so
		-- asking for something already answered is worse than not asking.
		--
		-- It tested for CONDITIONS, and a gold price is a FIELD -- so fifty of
		-- the sixty-eight it listed were priced, most of them from two
		-- independent sources agreeing to the copper. Exactly the shape of the
		-- contribution counter that measured "has no conditions" while calling
		-- itself "has no price".
		if not (MM.Diagnostics and MM.Diagnostics.IsUnpriced) then
			return nil, "IsUnpriced not present"
		end
		if #recs == 0 then return nil, "no records loaded" end
		local listed, priced, bad = 0, 0, nil
		for _, r in ipairs(recs) do
			if r.goldCost then priced = priced + 1 end
			if MM.Diagnostics.IsUnpriced(r) then
				listed = listed + 1
				if r.goldCost then bad = bad or (r.name .. " costs " .. r.goldCost .. "g") end
				for _, c in ipairs(r.conditions or {}) do
					if (c.type == "CURRENCY" or c.type == "ITEM") and c.amount then
						bad = bad or (r.name .. " already states its cost")
					end
				end
			end
		end
		if bad then return false, bad end
		if priced == 0 then return false, "no record carries a gold price" end
		return true, ("%d still unpriced; none of the %d priced ones are on the list")
			:format(listed, priced)
	end)

	check("The two unpriced lists agree with each other", function()
		-- The contribution export and the diagnostic list both answer "which
		-- records have no cost we can read", and they answered differently:
		-- seven records carrying an explicit reason they cannot be priced --
		-- fished, solved, dropped rather than sold -- were absent from one and
		-- present in the other.
		--
		-- The predicate was pulled into a single function precisely so this
		-- could not happen, and it still did, because the extraction copied the
		-- old body verbatim and the old body never read the field. A shared
		-- function is not agreement; being asked the same question is.
		if #recs == 0 then return nil, "no records loaded" end
		if not (MM.Diagnostics and MM.Diagnostics.IsUnpriced) then
			return nil, "IsUnpriced not present"
		end
		local disagree, example, listed = 0, nil, 0
		for _, r in ipairs(recs) do
			local diag = MM.Diagnostics.IsUnpriced(r)
			if diag then listed = listed + 1 end
			-- Anything stating WHY it has no price must be absent from both.
			if diag and r.unpriced then
				disagree = disagree + 1
				example = example or (r.name .. " — " .. tostring(r.unpriced))
			end
		end
		if disagree > 0 then
			return false, ("%d records say why they are unpriced and are asked anyway, e.g. %s")
				:format(disagree, example)
		end
		return true, ("%d genuinely unpriced; every explained one is absent from both")
			:format(listed)
	end)

	check("A record that has been answered is not asked about again", function()
		-- Three columns of this file have now made the same mistake: the price
		-- list asked for prices already recorded, the craft list asked for
		-- reagents that do not exist, and the drop-rate list kept asking for
		-- rates on records where everything obtainable had already been
		-- supplied. Each time the fix was a field saying why, and each time
		-- exactly one of the two lists that ask the question read it.
		--
		-- So this asserts the invariant directly rather than trusting that the
		-- next field will be wired into both places: nothing carrying a stated
		-- reason may appear in either list.
		if #recs == 0 then return nil, "no records loaded" end
		local CHANCY = { DROP = true, RARE = true, ZONEDROP = true }
		local asked, answered, bad = 0, 0, nil
		for _, r in ipairs(recs) do
			if r.obtainable and CHANCY[r.category] then
				if r.rateReason then
					answered = answered + 1
					-- Present in the list is the failure; the list is defined by
					-- the same predicate both readers use.
					if not r.dropRate then
						-- Must be excluded BECAUSE of the reason, not in spite
						-- of it -- so re-derive what the exporter would list.
						local wouldList = not r.dropRate and not r.rateReason
						if wouldList then
							bad = bad or (r.name .. " is answered and still listed")
						end
					end
				elseif not r.dropRate then
					asked = asked + 1
				end
			end
		end
		if bad then return false, bad end
		if answered == 0 then return nil, "no record carries a stated reason yet" end
		return true, ("%d still to answer; %d answered and absent from the list")
			:format(asked, answered)
	end)

	check("A mount locked to another class is not reported as missing", function()
		-- The audit listed twenty records as "obtainable, this faction, and
		-- still missing", which reads as twenty mounts the addon cannot see. A
		-- Druid form and a heritage mount are absent from a Hunter's journal
		-- for the same reason the other faction's are -- and calling them
		-- suspects sends someone hunting for a typo in a name that is correct.
		--
		-- Asserts the gates identify a real mismatch AND refuse to invent one:
		-- a record naming this character's own class or race must come back
		-- clean, or every mount would be excused and the list would empty
		-- itself into looking healthy.
		local QG = MM.QuestGate
		if not (QG and QG.WrongClass and QG.WrongRace) then
			return false, "class/race gates not present"
		end
		local myClass = select(2, UnitClass("player"))
		local myRace = select(2, UnitRace("player"))
		if not (myClass and myRace) then return nil, "class or race unreadable" end

		local mine = { category = "CLASS", source = "", notes = myClass:lower() .. " only." }
		if QG.WrongClass(mine) then
			return false, "claimed a mismatch against this character's own class"
		end
		local other = (myClass == "DRUID") and "paladin" or "druid"
		local theirs = { category = "CLASS", source = "", notes = other .. " only." }
		if not QG.WrongClass(theirs) then
			return false, "did not spot a " .. other .. "-only mount"
		end
		-- Race wording has to be a REQUIREMENT. "Sold in Silvermoon" names no
		-- race; "Requires a Blood Elf character" does.
		local place = { notes = "Sold in Silvermoon City by a blood elf vendor." }
		if QG.WrongRace(place) then
			return false, "read a race requirement out of a vendor's description"
		end
		return true, ("class and race gates agree with %s %s"):format(myRace, myClass)
	end)

	check("A repeated notice stays quiet, a changed one speaks", function()
		-- Chat is shared with the guild, the group, loot and every other addon,
		-- so a line has to earn its place. Two notices were repeating at every
		-- login and at every flight master, saying what was only news once.
		--
		-- Silencing them FOREVER is the opposite mistake, and a quieter one: a
		-- player who learns flight points in a new expansion would be told
		-- nothing, for a reason decided months earlier that they cannot see.
		-- So this asserts both halves -- the same sentence twice is silent, a
		-- different sentence is not.
		--
		-- The failure mode is invisible to whoever writes the line, because
		-- their own client has already said it, and obvious to everyone else.
		if not MM.PrintIfNew then return false, "PrintIfNew not present" end
		local saved = MM.db.saidBefore
		MM.db.saidBefore = nil

		local key = "mm-selftest-notice"
		local first = MM:PrintIfNew(key, " ")
		local repeated = MM:PrintIfNew(key, " ")
		local changed = MM:PrintIfNew(key, "  ")
		local repeatedAgain = MM:PrintIfNew(key, "  ")
		local stored = MM.db.saidBefore and MM.db.saidBefore[key]

		MM.db.saidBefore = saved

		if not first then return false, "the first call said nothing" end
		if repeated then return false, "the same message printed twice" end
		if not changed then return false, "a changed message was swallowed" end
		if repeatedAgain then return false, "the changed message then repeated" end
		-- Written down, or a reload starts the whole cycle again.
		if not (stored and stored.msg and stored.at) then
			return false, "nothing was persisted, so a reload repeats it"
		end
		return true, "same message silent, changed message speaks, both remembered"
	end)

	check("No reputation is required twice on one mount", function()
		-- Forty-two records asked for one reputation under two spellings --
		-- "Venthyr" and "Venthyr Renown", a Netherwing row with a factionID and
		-- one without. The cost model prices every condition, so each of those
		-- mounts paid for its grind twice, and the id-less half was the more
		-- expensive one: with nothing to measure, it charges a full assumed
		-- grind on top of the real, measured figure.
		--
		-- They were invisible to the merge, which keys a condition by its id
		-- when it has one and by its name when it does not -- so the two rows
		-- are two keys, and adding the second never updated the first.
		if #recs == 0 then return nil, "no records loaded" end
		local dupes, example = 0, nil
		for _, r in ipairs(recs) do
			local seen = {}
			for _, c in ipairs(r.conditions or {}) do
				if c.type == "REP" or c.type == "RENOWN" then
					local name = c.factionName or c.faction or c.name or ""
					local key = name:lower():gsub("%s+renown$", ""):gsub("%s*%(.-%)%s*$", "")
					if seen[key] then
						dupes = dupes + 1
						example = example or (r.name .. ": " .. name)
					end
					seen[key] = true
				end
			end
		end
		if dupes > 0 then
			return false, ("%d duplicated gates, e.g. %s"):format(dupes, example)
		end
		return true, "every mount names each reputation once"
	end)

	check("Reputation requirements use the fields that get read", function()
		-- A condition written with `id`/`name`/`standing` instead of
		-- `factionID`/`factionName`/`standingName` looks complete and is
		-- invisible: every helper that reads a reputation looks for the
		-- canonical names, so the gate reports no standing, no renown level and
		-- no progress. Four mounts were in that state and nothing said so.
		if #recs == 0 then return nil, "no records loaded" end
		local bad, example, withID = 0, nil, 0
		for _, r in ipairs(recs) do
			for _, c in ipairs(r.conditions or {}) do
				if c.type == "REP" then
					if c.factionID then withID = withID + 1 end
					if not c.factionName or (c.standing and not c.standingName) then
						bad = bad + 1
						example = example or (r.name .. ": " .. tostring(c.name or c.factionName))
					end
				end
			end
		end
		if bad > 0 then return false, ("%d misnamed, e.g. %s"):format(bad, example) end
		return true, ("%d reputation gates the client can measure"):format(withID)
	end)

	check("Nothing is charged for the same cost twice", function()
		-- Seven records listed one cost under two spellings: an ITEM condition
		-- naming the token and a CURRENCY condition naming its plural. The
		-- planner prices every cost condition, so those mounts were ranked as
		-- costing double -- and a record with two requirements looks entirely
		-- normal, which is why they lasted.
		--
		-- THE PAIR OF TYPES IS NOT THE POINT. This first compared ITEM against
		-- CURRENCY, and the next duplicate to appear was ITEM against MATERIAL
		-- -- twenty-two Protoform mounts carrying Genesis Mote as both, which
		-- this test watched go past. A guard that enumerates the pairs it knows
		-- about will always miss the pair it does not, so it now compares any
		-- two cost conditions naming one thing, whatever their types.
		if #recs == 0 then return nil, "no records loaded" end
		local COST = { ITEM = true, CURRENCY = true, MATERIAL = true }
		local dupes, example = 0, nil
		for _, r in ipairs(recs) do
			local seen = {}
			for _, c in ipairs(r.conditions or {}) do
				if COST[c.type] and c.name then
					local key = c.name:lower():gsub("s$", "")
					if seen[key] and seen[key] ~= c.type then
						dupes = dupes + 1
						example = example or ("%s: %s as %s and %s")
							:format(r.name, c.name, seen[key], c.type)
					end
					seen[key] = c.type
				end
			end
		end
		if dupes > 0 then
			return false, ("%d duplicated costs, e.g. %s"):format(dupes, example)
		end
		return true, "no cost appears under two condition types"
	end)

	check("Every requirement can be asked about", function()
		-- A condition that names something and carries no id is a string. The
		-- client cannot be asked whether it is met, so the planner falls back to
		-- an assumption for the whole thing.
		--
		-- Two of these were written off as unanswerable and neither was. The
		-- covenants are not reputations, which was true and became an excuse --
		-- C_CovenantSanctumUI reports renown directly. "Guild" matched two
		-- factions, one of which is a header with no bar at all.
		--
		-- Asserted at zero rather than at a count, because a count that drifts
		-- upward reads as normal.
		if #recs == 0 then return nil, "no records loaded" end
		local ASKABLE = { ITEM = true, CURRENCY = true, ACHIEVEMENT = true,
			QUEST = true, COVENANT = true }
		local bare, example = 0, nil
		for _, r in ipairs(recs) do
			for _, c in ipairs(r.conditions or {}) do
				if ASKABLE[c.type] and not c.id and not c.idAlliance then
					bare = bare + 1
					example = example or ("%s: %s %s"):format(r.name, c.type, c.name or "?")
				elseif c.type == "REP" and not c.factionID
					and not c.factionIDAlliance then
					bare = bare + 1
					example = example or ("%s: REP %s")
						:format(r.name, c.factionName or c.name or "?")
				end
			end
		end
		if bare > 0 then
			return false, ("%d requirements have no id, e.g. %s"):format(bare, example)
		end
		return true, "every requirement carries an id the client can resolve"
	end)

	check("Promoting a faction variant keeps the ids it was given", function()
		-- Every id, amount and factionID the data layers apply lands on the
		-- CANONICAL record. Promoting the other faction's variant replaced that
		-- table wholesale, so one side got a requirement with nothing on it:
		-- three Vicious mounts asked for a "Vicious Saddle" with no item id, on
		-- Horde only.
		--
		-- That is the worst shape a bug can have here -- correct on the client
		-- you are testing, broken on the one you are not, and identical in the
		-- data file. Assert it from the character actually running.
		if #recs == 0 then return nil, "no records loaded" end
		local bare, example, promoted = 0, nil, 0
		for _, r in ipairs(recs) do
			-- A record that HAD an alternate for this side has been promoted;
			-- altSources still holds the ones that were not taken.
			if r.altSources then promoted = promoted + 1 end
			for _, c in ipairs(r.conditions or {}) do
				-- A cost that names something and cannot say what it is cannot
				-- be counted against the player's bags or balance.
				if (c.type == "ITEM" or c.type == "CURRENCY") and c.name
					and not c.id and not c.idAlliance and not c.idHorde
					and c.amount then
					bare = bare + 1
					example = example or (r.name .. ": " .. c.name)
				end
			end
		end
		-- Two are known and explained in the data: Glowing Moths are world
		-- objects rather than a currency, so no id exists to find.
		if bare > 2 then
			return false, ("%d costs name something with no id, e.g. %s"):format(bare, example)
		end
		return true, ("%d records carry alternates; %d unidentified costs, both explained")
			:format(promoted, bare)
	end)

	check("Crafts know what they are made of", function()
		-- Crafted mounts were costed on a flat guess because reagent lists are
		-- not invented here, and the only route to real ones was a player
		-- opening a profession window for every recipe in turn.
		--
		-- They come from the client's own tables now -- the spell that creates
		-- the teaching item, that spell's reagents, and their names. This
		-- asserts the import is actually present and actually reaches Crafting,
		-- because a MATERIAL condition that MaterialsFor cannot read would look
		-- identical in the data and change nothing in the plan.
		if #recs == 0 then return nil, "no records loaded" end
		local crafts, withMats, gathered = 0, 0, 0
		local sample
		for _, r in ipairs(recs) do
			if r.obtainable and r.category == "PROFESSION" then
				crafts = crafts + 1
				if r.unpriced then gathered = gathered + 1 end
				for _, c in ipairs(r.conditions or {}) do
					if c.type == "MATERIAL" and c.itemID and (c.count or 0) > 0 then
						withMats = withMats + 1
						sample = sample or r
						break
					end
				end
			end
		end
		if withMats == 0 then return false, "no craft carries a reagent list" end
		-- Reached through the same call the planner uses, not by reading the
		-- record again: the point is that Crafting can see it.
		local mats, known = MM.Crafting.MaterialsFor(sample)
		if not (known and mats and #mats > 0) then
			return false, ("%s has reagents that Crafting cannot read"):format(sample.name)
		end
		return true, ("%d of %d crafts have real reagents, %d are gathered; %s reads %d")
			:format(withMats, crafts, gathered, sample.name, #mats)
	end)

	check("Faction-split requirements resolved to this character's side", function()
		-- Some requirements exist twice, one copy per faction: the collection
		-- achievements, the Outland quartermaster talbuks, the Argent
		-- Tournament city mounts. The data carries both and login picks one.
		--
		-- The failure this catches is silent and total: if the pick never runs,
		-- the condition keeps a name and no id, the client cannot be asked
		-- whether it is met, and the planner treats a finished achievement as
		-- outstanding forever.
		if #recs == 0 then return nil, "no records loaded" end
		local paired, resolved, wrong = 0, 0, nil
		local side = UnitFactionGroup and UnitFactionGroup("player")
		if not (side == "Alliance" or side == "Horde") then
			return nil, "player faction unknown"
		end
		local want = (side == "Alliance") and "idAlliance" or "idHorde"
		for _, r in ipairs(recs) do
			for _, c in ipairs(r.conditions or {}) do
				if c.idAlliance and c.idHorde then
					paired = paired + 1
					if c.id == c[want] then
						resolved = resolved + 1
					else
						wrong = wrong or ("%s: %s is %s, expected %s")
							:format(r.name, c.name or "?", tostring(c.id), tostring(c[want]))
					end
				end
			end
		end
		if paired == 0 then return false, "no faction-split requirements in the data" end
		if resolved < paired then return false, wrong end
		return true, ("%d requirements, all on the %s id"):format(paired, side)
	end)

	check("Every locationless record says why", function()
		-- A record with no place is either a gap someone can close or a
		-- category error nobody can. The difference has to be written down,
		-- because a count that mixes them tells players to go and find
		-- coordinates for a mount bought from Blizzard.
		--
		-- This asserts the remainder is explained: anything left without a
		-- location, outside the categories that cannot have one, carries a
		-- reason in the record.
		if #recs == 0 then return nil, "no records loaded" end
		local PLACELESS = {
			STORE = true, PROMOTION = true, TCG = true, ACHIEVEMENT = true,
			PROFESSION = true, PVP = true, CLASS = true, REMOVED = true,
			TRADINGPOST = true,
		}
		local open, example = 0, nil
		for _, r in ipairs(recs) do
			if r.obtainable and not r.zone and not r.vendor
				and not r.noLocationReason and not PLACELESS[r.category] then
				open = open + 1
				example = example or (r.name .. " (" .. tostring(r.category) .. ")")
			end
		end
		if open > 0 then
			return false, ("%d unexplained, e.g. %s"):format(open, example)
		end
		return true, "no unexplained locationless records"
	end)

	check("Gold prices are plausible", function()
		-- goldCost is read straight into the planner's cost model, so a
		-- mis-parsed price is a silently wrong plan rather than an error. The
		-- import that produced most of these came off a page whose thousands
		-- separator is a SPACE -- "10 000" reads as ten to anything that stops
		-- at the first run of digits.
		--
		-- A thousandfold error cannot be spotted by eye in a list of prices, so
		-- assert the shape instead: whole gold, above zero, and inside the
		-- range the game actually charges.
		if #recs == 0 then return nil, "no records loaded" end
		local n, bad = 0, nil
		for _, r in ipairs(recs) do
			local g = r.goldCost
			if g then
				n = n + 1
				if type(g) ~= "number" or g <= 0 or g ~= math.floor(g) or g > 5000000 then
					bad = bad or ("%s: %s"):format(r.name, tostring(g))
				end
			end
		end
		if n == 0 then return false, "no record carries a gold price" end
		if bad then return false, bad end
		return true, ("%d gold prices, all whole and in range"):format(n)
	end)

	check("Journal coverage", function()
		local S = MM.Scanner
		-- S.mounts, not S.entries. The first draft of this check used a field
		-- name that does not exist, so it reported "scan has not run yet"
		-- forever -- a green-looking test that measured nothing. Any check that
		-- can only ever return its own skip path is worse than no check.
		if not (S and S.ready and S.mounts) then return nil, "scan has not run yet" end
		-- Count the WHOLE journal including entries hidden for this character.
		-- Measuring only the displayed subset flattered coverage, because the
		-- hidden ones are exactly the records the audit was calling orphans.
		local total, matched = 0, 0
		for _, e in pairs(S.byMountID or {}) do
			total = total + 1
			if e.rec and not e.rec.stub then matched = matched + 1 end
		end
		if total == 0 then return nil, "journal reported no mounts" end
		local pct = matched / total * 100
		-- below 90% means the journal has drifted well past our database
		return pct >= 90, ("%d of %d journal mounts catalogued (%.1f%%)")
			:format(matched, total, pct)
	end)

	check("Every mount the Quantum Courser can give is still in the pool", function()
		-- Reins of the Quantum Courser grants one of sixteen mounts, and the pool
		-- is written in ITEM names everywhere it is published -- eleven of the
		-- sixteen differ from the mount's own name. Each is resolved by name at
		-- load, so a rename anywhere in those sixteen would drop a member and
		-- shrink the pool without a word.
		--
		-- The item is not modelled as a cost. This asserts only that the source
		-- reached every mount it belongs to.
		--
		-- The pool is BUILT at build time and shipped as a declaration, because
		-- the flat database is the applied result and carries no trace of what
		-- applied it. Read against the live database, this asks the question
		-- that still matters here: are all sixteen present, and does each one
		-- actually carry the source that was appended to it.
		local pool = MM.quantumCourserPool or {}
		if #pool == 0 then return nil, "pool not declared" end
		if #pool ~= 16 then
			return false, ("the pool is sixteen mounts, %d declared"):format(#pool)
		end
		local gone, silent = {}, {}
		for _, name in ipairs(pool) do
			local rec = MM.DBByName[name:lower()]
			if not rec then
				gone[#gone + 1] = name
			elseif not (rec.notes and rec.notes:find("Quantum Courser", 1, true)) then
				silent[#silent + 1] = name
			end
		end
		if #gone > 0 then
			return false, ("%d pool member(s) are not in the database: %s"):format(
				#gone, table.concat(gone, ", ", 1, math.min(3, #gone)))
		end
		if #silent > 0 then
			return false, ("%d pool member(s) do not name the source: %s"):format(
				#silent, table.concat(silent, ", ", 1, math.min(3, #silent)))
		end
		return true, ("%d mounts carry the Quantum Courser as a source"):format(#pool)
	end)

	check("A record removed as a phantom was actually there", function()
		-- Seven records named a mount the game does not have -- the same mount
		-- catalogued a second time under a spelling Mount.db2 has never used,
		-- once as the ITEM that teaches it. They are removed rather than
		-- flagged, because a phantom is indistinguishable from a coverage gap
		-- in every count that walks the database.
		--
		-- Removing by NAME is the risk this guards. A rename upstream, or a
		-- typo here, would match nothing and drop nothing, and the only visible
		-- effect would be the audit quietly listing a mount as missing again.
		-- RemoveMount records both the hits and the misses so neither is silent.
		--
		-- The removals happen at build time, so nothing here ever calls
		-- RemoveMount and a name that matched nothing cannot be seen from the
		-- client at all -- tools/flatten_data.lua refuses to install a build
		-- where one did. What IS visible here is the result, and the list is
		-- shipped as a declaration so it can be checked against the database
		-- the player actually loaded.
		local removed = MM.removedPhantoms or {}
		if #removed == 0 then return nil, "no phantoms declared" end
		-- A phantom that is back is the whole failure, restated: a record naming
		-- a mount the game does not have, counted as a mount we are missing.
		local returned = {}
		for _, r in ipairs(removed) do
			if r.name and MM.DBByName[r.name:lower()] then
				returned[#returned + 1] = r.name
			end
		end
		if #returned > 0 then
			return false, ("%d removed phantom(s) are in the database again: %s"):format(
				#returned, table.concat(returned, ", ", 1, math.min(3, #returned)))
		end
		-- And the mount each one was a copy OF must still be here, or the
		-- removal took the real record with it.
		-- Five of these have no counterpart at all -- a garrison ability, an
		-- item, two mounts that are not in the game -- so `real` is absent and
		-- there is nothing to check. The ones that ARE a copy of something must
		-- leave that something behind.
		local orphaned = {}
		for _, r in ipairs(removed) do
			if r.real and not MM.DBByName[r.real:lower()] then
				orphaned[#orphaned + 1] = r.real
			end
		end
		if #orphaned > 0 then
			return false, ("%d phantom(s) removed whose real record is gone too: %s")
				:format(#orphaned, table.concat(orphaned, ", "))
		end
		return true, ("%d phantom record(s) removed, every real counterpart still present")
			:format(#removed)
	end)

	check("A Timewalking mount is not offered outside a Timewalking week", function()
		-- REPORTED FROM PLAY: "you are still showing the timewalking mounts when
		-- 'available now' is checked."
		--
		-- The window was asked about for `category == "TIMEWALKING"` and nowhere
		-- else, and thirteen of these are catalogued VENDOR, CURRENCY or
		-- ACHIEVEMENT because that is how you get them. They carry
		-- `holidayGate = "Timewalking"` now, and this reads the finished database
		-- rather than the list that set it: what matters is that no record which
		-- names a Timewalking week in its own text can report AVAILABLE while no
		-- week is running.
		local declared = MM.timewalkingGated or {}
		if #declared == 0 then return nil, "no Timewalking gates declared" end
		local ungated = {}
		for _, rec in ipairs(MM.DBList) do
			local src = (rec.source or ""):lower()
			if rec.obtainable ~= false and src:find("timewalking", 1, true)
				and rec.category ~= "TIMEWALKING"
				and rec.holidayGate ~= "Timewalking" then
				ungated[#ungated + 1] = rec.name
			end
		end
		-- One is deliberately not gated and says why in Data_99zzZr: a
		-- world-boss drop whose record does not state a week.
		local KNOWN = { ["Illidari Doomhawk"] = true }
		local unexpected = {}
		for _, n in ipairs(ungated) do
			if not KNOWN[n] then unexpected[#unexpected + 1] = n end
		end
		if #unexpected > 0 then
			return false, ("%d Timewalking mount(s) with no window gate: %s"):format(
				#unexpected, table.concat(unexpected, ", ", 1, math.min(3, #unexpected)))
		end
		-- And the gate must actually be doing something: with no week running,
		-- every gated record has to refuse.
		if not MM.Timewalking.IsActive() then
			local leaking, asked = {}, 0
			for _, name in ipairs(declared) do
				local entry = MM.Scanner and MM.Scanner.byName
					and MM.Scanner.byName[name:lower()]
				if entry then
					asked = asked + 1
					if MM.Availability.GetStatus(entry) == "AVAILABLE" then
						leaking[#leaking + 1] = name
					end
				end
			end
			-- A check that asked nothing has proved nothing. Without this, an
			-- empty index reads exactly like thirteen mounts behaving.
			if asked == 0 then
				return nil, "the journal index has no entry for any of them yet"
			end
			if #leaking > 0 then
				return false, ("%d gated mount(s) still read as available: %s"):format(
					#leaking, table.concat(leaking, ", ", 1, math.min(3, #leaking)))
			end
			return true, ("%d of %d gated checked against the client, no week running — none reads as available")
				:format(asked, #declared)
		end
		return true, ("%d Timewalking mounts gated on the window; a week is running")
			:format(#declared)
	end)

	check("Journal text carries no escape the game meant to render", function()
		-- Blizzard writes journal source text with its own markup: colour codes,
		-- money and atlas textures, and "|n" for a line break. A frame RENDERS
		-- all of that, so an escape left in the string is invisible until it
		-- reaches somewhere that does not render -- and then it is a line break
		-- in the middle of a report entry, or, from /mm stubs, a Lua string
		-- literal split across three lines that will not compile.
		--
		-- "|n" was the one nothing matched, because it is not a newline
		-- character and every reader here was looking for one.
		local S = MM.Scanner
		if not (S and S.ready and S.byMountID) then return nil, "scan has not run yet" end
		local checked, bad = 0, {}
		for _, e in pairs(S.byMountID) do
			local src = e.rec and e.rec.source
			if type(src) == "string" then
				checked = checked + 1
				local esc = src:match("|%a")
				if esc and #bad < 3 then
					bad[#bad + 1] = ("%s carries %q"):format(e.name or "?", esc)
				end
			end
		end
		if checked == 0 then return nil, "no journal entry carries source text" end
		if #bad > 0 then
			return false, ("%d source text(s) still carry markup: %s")
				:format(#bad, table.concat(bad, ", "))
		end
		return true, ("%d journal source text(s), none carrying markup"):format(checked)
	end)
end

------------------------------------------------------------
-- Logic: pure functions, known inputs, known answers
------------------------------------------------------------
local function approx(a, b) return math.abs(a - b) < 0.001 end

-- The first routed stop that has somewhere to be.
--
-- Stands in for Router:Current() in checks that only need A destination to
-- price. Current() is gated on the player having started a route, which is
-- correct for the arrow and wrong as a test precondition -- it made two travel
-- checks unrunnable unless somebody happened to be mid-route when they typed
-- /mm report.
local function firstPlacedStop()
	local R = MM.Router
	for _, stop in ipairs(R and R.route or {}) do
		if stop.world then return stop end
	end
end

local function runLogic()
	current = "LOGIC"

	check("Value cost falls as payoff rises", function()
		local P = MM.Planner
		local cheap, dear = P.ValueScoreFromVPM(1.0), P.ValueScoreFromVPM(0.01)
		if not (cheap < dear) then
			return false, ("%.1f should be below %.1f"):format(cheap, dear)
		end
		-- batching depends on this: summed vpm must cost less than any part
		local one, three = P.ValueScoreFromVPM(0.1), P.ValueScoreFromVPM(0.3)
		return three < one, ("1 goal %.0f, 3 goals %.0f"):format(one, three)
	end)

	-- PROGRESS PRICING
	--
	-- Every one of these guards a bug that shipped. They use synthetic records
	-- so they assert the ARITHMETIC rather than whatever standing the character
	-- running /mm happens to hold.
	check("Rep gating recognises rep, renown and paragon", function()
		local P = MM.Planner
		local rep = { name = "t", conditions = { { type = "REP", factionID = 1 } } }
		local drop = { name = "t", conditions = { { type = "DROP" } } }
		if not P.IsRepGated(rep) then return false, "plain REP not detected" end
		if P.IsRepGated(drop) then return false, "DROP wrongly detected" end
		return true, "REP yes, DROP no"
	end)

	check("Plain rep is not charged a paragon bar", function()
		-- THE REGRESSION: generalising paragon pricing to all reputations first
		-- added a full paragon bar to every one of them, overcharging plain rep
		-- and renown by five hours and burying them in the ranking.
		local P = MM.Planner
		local plain = { name = "t", conditions = { { type = "REP", factionID = 0 } } }
		-- IsParagon reads the source text, so the fixture must too.
		local para = { name = "t", source = "Paragon cache, Death's Advance",
			conditions = { { type = "REP", factionID = 0 } } }
		local a, b = P.RepRemainingMinutes(plain), P.RepRemainingMinutes(para)
		if not (a and b) then return false, "no figure returned" end
		if not (b > a) then
			return false, ("paragon %.0f must exceed plain %.0f"):format(b, a)
		end
		return true, ("plain %.0fh, paragon %.0fh"):format(a / 60, b / 60)
	end)

	check("PvP cost is matches, not a flat season", function()
		local P = MM.Planner
		-- 1000 to earn, 100 a win, 50 a loss. At an even split that is 75 a
		-- match, so 14 matches; at 12 minutes each, 168 minutes.
		local rec = { name = "t", pvpBarTotal = 1000, pvpPerWin = 100,
			pvpPerLoss = 50, pvpMatchMinutes = 12 }
		local mins, matches = P.PvpRemainingMinutes(rec, 0)
		if not mins then return false, "no figure returned" end
		if matches ~= 14 then
			return false, ("expected 14 matches, got %s"):format(tostring(matches))
		end
		-- Half the bar already filled must cost about half as much.
		local half = P.PvpRemainingMinutes(rec, 0.5)
		if not (half < mins) then return false, "progress did not reduce cost" end
		return true, ("%d matches, %.0f min; half filled %.0f min")
			:format(matches, mins, half)
	end)

	check("PvP win rate reads the client, or says it cannot", function()
		local PS = MM.PvpStats
		if not PS then return false, "PvpStats missing" end
		local rate, played, bracket = PS.Best()
		local fallback = PS.WinRate()
		if fallback < 0 or fallback > 1 then
			return false, ("win rate out of range: %s"):format(tostring(fallback))
		end
		if not rate then
			-- Not a failure: a character with no rated games has no rate, and
			-- saying so is the correct answer.
			return nil, "no rated games this season; assuming 50%"
		end
		return true, ("%.0f%% over %d games (bracket %d)")
			:format(rate * 100, played, bracket)
	end)

	check("PvP refuses to guess without data", function()
		-- No bar total means no honest answer. Returning a number here would be
		-- inventing the season, which is how the reagent lists went wrong.
		local P = MM.Planner
		return P.PvpRemainingMinutes({ name = "t" }, 0) == nil, "nil without inputs"
	end)

	check("Calendar-gated rep prices days, not hours", function()
		local P = MM.Planner
		local rec = { name = "t", repPacing = "DAILY", repDaysRemaining = 20 }
		if not P.RepIsCalendarGated(rec) then return false, "not detected" end
		local mins, days = P.CalendarRepMinutes(rec, 0)
		if not mins then return false, "no figure returned" end
		local part = P.CalendarRepMinutes(rec, 0.75)
		if not (part and part < mins) then
			return false, "progress did not reduce the day count"
		end
		return true, ("%d days -> %.0f min; 75%% done -> %.0f min")
			:format(days, mins, part)
	end)

	check("Urgency scales with the window left", function()
		-- Daily and weekly both returned URGENCY.LOCKOUT and both got the same
		-- flat boost, so a Thursday weekly pushed as hard as a Monday-night one.
		local R = MM.Router
		if not R.WindowPressure then return nil, "not exposed for testing" end
		local daily = R.WindowPressure({ attempts = "DAILY" })
		local weekly = R.WindowPressure({ attempts = "WEEKLY" })
		if not (daily and weekly) then return false, "no pressure returned" end
		if daily < 0 or daily > 1 or weekly < 0 or weekly > 1 then
			return false, ("out of range: %.2f / %.2f"):format(daily, weekly)
		end
		return true, ("daily %.0f%% spent, weekly %.0f%% spent")
			:format(daily * 100, weekly * 100)
	end)

	check("Time formatting", function()
		local s = U.FormatSeconds(3661)
		return type(s) == "string" and #s > 0, s
	end)

	check("Thousands separators", function()
		local s = U.Comma(1234567)
		return s:find("1") ~= nil and #s > 7, s
	end)

	check("Category filter groups", function()
		if not MM.CategoryMatch(nil, "DROP") then return false, "nil filter should match all" end
		if not MM.CategoryMatch("GROUP_DROPS", "RARE") then return false, "RARE is a drop" end
		if MM.CategoryMatch("GROUP_DROPS", "VENDOR") then return false, "VENDOR is not a drop" end
		return true
	end)

	check("Unusable zone falls back to vendor", function()
		-- a zone with no resolvable map must not beat a known vendor location
		local rec = { zone = { name = "Not A Real Zone / Compound", x = 1, y = 2 },
			vendor = "definitely-not-a-vendor" }
		local loc = MM.GetRecordLocation(rec)
		return loc ~= nil, "returned a location table"
	end)

	check("Acquire chain reports progress", function()
		local line, need = MM.Acquire.ChainProgress({
			acquire = { item = 0, name = "Test Token", count = 5 } })
		if not line then return false, "no progress line" end
		return need == 5, line
	end)

	check("Prerequisite gate blocks wrong faction", function()
		local other = (MM.playerFaction == "Horde") and "Alliance" or "Horde"
		local why = MM.QuestGate.HardGate({ faction = other, category = "VENDOR" })
		if not why then return false, "wrong-faction record was not gated" end
		local fine = MM.QuestGate.HardGate({ faction = MM.playerFaction, category = "VENDOR" })
		return fine == nil, why
	end)

	check("Reputation grind is not a gate", function()
		-- a rep bar is the work, not a blocker; gating it would empty the plan
		local why = MM.QuestGate.HardGate({
			category = "REP",
			conditions = { { type = "REP", name = "Some Faction", standing = "Exalted" } },
		})
		return why == nil, why and ("wrongly gated: " .. why) or nil
	end)

	check("Calling gate refuses to guess", function()
		local state = MM.Callings.Evaluate({ zone = "Nowhere", mapID = -1 })
		-- must never answer AVAILABLE for a zone it cannot confirm
		return state ~= "AVAILABLE", "returned " .. tostring(state)
	end)

	check("Unrated drops are not treated as certain", function()
		-- A missing dropRate scored as chance = 1, giving an unrated mount a
		-- value-per-minute no real farm could match. That is how a current-tier
		-- Mythic+ mount reached the top of a route.
		local rated = { rec = { category = "DROP", dropRate = 100, timePerAttempt = 20,
			expansion = 3 } }
		local unrated = { rec = { category = "DROP", timePerAttempt = 20, expansion = 3 } }
		local a, b = MM.Planner.ValuePerMinute(rated), MM.Planner.ValuePerMinute(unrated)
		if not (a > b) then
			return false, ("unrated %.4f is not below a guaranteed %.4f"):format(b, a)
		end
		return true, ("guaranteed %.3f vs unrated %.3f"):format(a, b)
	end)

	check("Priority order reaches the route", function()
		-- Routing weighed payoff and travel and nothing else, so the priority
		-- list only ever sorted the "easiest" view.
		local easy = { rec = { category = "DROP", dropRate = 100, timePerAttempt = 20,
			expansion = 3 } }
		local group = { rec = { category = "DROP", dropRate = 100, timePerAttempt = 20,
			expansion = 3, solo = false } }
		local a, b = MM.Planner.ValuePerMinute(easy), MM.Planner.ValuePerMinute(group)
		if not (a > b) then
			return false, ("group content scores %.3f against %.3f — tier is ignored"):format(b, a)
		end
		return true, ("solo %.3f vs needs-a-group %.3f"):format(a, b)
	end)

	check("Tooltips do not say the same thing twice", function()
		-- the player: tooltips must not repeat themselves. The exact-match guard was
		-- never enough -- the same sentence arrives worded two ways.
		local cases = {
			{ "Achievement: Glory of the Uldir Raider",
			  "Reward from the achievement Glory of the Uldir Raider", true },
			{ "Exalted with Ramkahen",
			  "Sold by Blacksmith Abasi at Exalted with Ramkahen", true },
			{ "Drops from Poseidus in Vashj'ir",
			  "Buy from the auction house", false },
			-- progress is never a restatement: it is the one thing the source
			-- cannot know
			{ "Test Token: 0 / 5", "Collect 5 Test Tokens", false },
			{ "Timewarped Badge: 0 / 5000", "Bought from the Timewalking vendor", false },
		}
		for _, c in ipairs(cases) do
			local got = U.Restates(c[1], c[2])
			if got ~= c[3] then
				return false, ("%q vs %q gave %s"):format(c[1], c[2], tostring(got))
			end
		end
		return true, ("%d wording cases behave"):format(#cases)
	end)

	check("Preference never bends expected mounts", function()
		-- the objection, made into a rule: priority and reluctance are how we
		-- CHOOSE, never what we CLAIM. Expected mounts is a physical quantity the
		-- UI reports as fact, and the day it starts absorbing taste is the day
		-- the summary stops being true.
		local R = MM.Router
		local stop = { tier = MM.Planner.TIER.PICKUP, handicap = 0, mounts = 0.5,
			workMinutes = 10, world = nil }
		local plainA = R.Density(stop, 5, 10)
		local saved = MM.db.weights
		-- 1.0, not 1.5: at the top of the range the ordering goes lexicographic
		-- and the score is a rank block plus a remainder, so "lower than the
		-- density" stops being the right question. Strict mode has its own check.
		MM.db.weights = { schema = 2, priority = 1.0 }
		MM:Fire("MM_WEIGHTS_CHANGED")
		stop.tier = MM.Planner.TIER.GROUP
		stop.handicap = 3000
		local plainB = R.Density(stop, 5, 10)
		local scored = R.SelectionScore(stop, 5, 10)
		MM.db.weights = saved
		MM:Fire("MM_WEIGHTS_CHANGED")
		if math.abs(plainA - plainB) > 0.0001 then
			return false, ("density moved with preference: %.4f vs %.4f"):format(plainA, plainB)
		end
		if scored >= plainB then
			return false, "preference did not affect the selection score at all"
		end
		return true, ("density %.4f unchanged, selection %.4f"):format(plainB, scored)
	end)

	check("Every goal can explain itself", function()
		-- "Priority 7" with no reasoning is the opaque case players hit. The rule
		-- is that the explanation must always exist and must always name the
		-- points, so the answer to "why is this here" and the answer to "what
		-- do I change" are the same screen.
		local checked = 0
		for _, entry in ipairs(MM.Scanner.mounts) do
			if not entry.collected and entry.rec then
				local lines = MM.Planner.Explain(entry)
				if type(lines) ~= "table" or #lines < 3 then
					return false, ("%s explained itself in %d lines")
						:format(entry.name, lines and #lines or 0)
				end
				for _, l in ipairs(lines) do
					if type(l.text) ~= "string" or l.text == "" then
						return false, entry.name .. " produced an empty line"
					end
				end
				-- The cost breakdown is no longer asserted here: it was removed
				-- from the player tooltip as unactionable, and the invariant it
				-- guarded -- breakdown matching the score actually used -- is
				-- checked directly against CostParts by the next test.
				checked = checked + 1
				if checked >= 60 then break end
			end
		end
		if checked == 0 then return nil, "nothing uncollected to explain" end
		return true, ("%d goals itemise their own placement"):format(checked)
	end)

	check("Itemised cost matches the score used", function()
		-- If the tooltip's breakdown and the ranking ever diverge, the tooltip
		-- becomes a lie that is harder to catch than a wrong number.
		for _, entry in ipairs(MM.Scanner.mounts) do
			if not entry.collected and entry.rec then
				local total, parts = MM.Planner.CostParts(entry)
				local sum = 0
				for _, p in ipairs(parts) do sum = sum + p[2] end
				if math.abs(sum - total) > 0.001 then
					return false, ("%s: parts sum %.1f, total %.1f")
						:format(entry.name, sum, total)
				end
				return true, ("%d cost terms, summing exactly"):format(#parts)
			end
		end
		return nil, "nothing uncollected to check"
	end)

	check("Every weight reaches the route", function()
		-- Requirement — i changed a bunch of weights and priorities and nothing really
		-- changed -- that makes no sense. He was right: routing consumed only
		-- the priority order and deadline pressure, so four of six sliders moved
		-- nothing but the Easiest list. This walks each weight and demands that
		-- something the ROUTER reads actually moves.
		local W, P, R = MM.Weights, MM.Planner, MM.Router
		local subject
		for _, e in ipairs(MM.Scanner.mounts) do
			if not e.collected and e.rec and e.rec.expansion then subject = e break end
		end
		if not subject then return nil, "no uncollected goal to measure" end

		local saved = MM.db.weights
		local function probe(key, value, read)
			MM.db.weights = { schema = 2 }
			MM:Fire("MM_WEIGHTS_CHANGED")
			local before = read()
			MM.db.weights = { schema = 2, [key] = value }
			MM:Fire("MM_WEIGHTS_CHANGED")
			local after = read()
			return before, after
		end

		local fakeStop = { tier = MM.Planner.TIER.GROUP, handicap = 500, mounts = 0.5,
			workMinutes = 120 }
		-- urgency only ever applies to a goal under a lockout, so probing it on
		-- a stop without one measures nothing.
		local urgentStop = { tier = MM.Planner.TIER.GROUP, handicap = 500, mounts = 0.5,
			workMinutes = 120, urgency = MM.Planner.URGENCY.LOCKOUT }
		local probes = {
			{ "travel scaling", "travel", 1000, function() return R.TravelScale() end },
			{ "travel detour", "travel", 1000, function() return R.DetourMinutes() end },
			-- effort and odds used to be probed through P.Handicap -- the
			-- PLANNER path, which feeds tooltips. Both passed for months while
			-- the router ignored them completely, and this check reported "all
			-- 8 weights move a routing input" the whole time. A probe that reads
			-- a different subsystem than the claim it is making is worse than no
			-- probe: it converts an open question into false assurance.
			--
			-- Every probe below now reads something the ROUTER itself consumes.
			{ "effort", "effort", 500, function() return R.StopValue(fakeStop) end },
			{ "odds", "odds", 0, function() return R.StopValue(fakeStop) end },
			{ "era", "era", 1000, function() return P.Handicap(subject) end },
			{ "priority", "priority", 1.5, function() return R.PreferenceDivisor(fakeStop) end },
			{ "urgency", "urgency", 0, function() return R.StopValue(urgentStop) end },
			{ "session", "session", 60, function() return R.SessionFit(fakeStop) end },
		}
		local dead = {}
		for _, pr in ipairs(probes) do
			local a, b = probe(pr[2], pr[3], pr[4])
			if math.abs((a or 0) - (b or 0)) < 0.0001 then dead[#dead + 1] = pr[1] end
		end
		MM.db.weights = saved
		MM:Fire("MM_WEIGHTS_CHANGED")

		if #dead > 0 then
			return false, "changes nothing the router reads: " .. table.concat(dead, ", ")
		end
		return true, ("all %d weights move a routing input"):format(#probes)
	end)

	check("A swapped route is not mistaken for a cache hit", function()
		-- The signature describes the PLAN. It was being read as proof that
		-- R.route was still the route it built, and anything that replaced the
		-- route without touching the signature got a cache hit on a route that
		-- was gone -- which is how the router model's six-goal sample survived
		-- into the planner.
		local R = MM.Router
		if not (R and R.Build) then return nil, "no router" end
		local real = routeInHand(R, 3)
		if real < 3 then return nil, "route too short to test" end
		-- Replace the route WITHOUT touching the signature, exactly as the
		-- harness used to, and ask for a build.
		local stolen = {}
		for i = 1, 2 do stolen[i] = R.route[i] end
		R.route = stolen
		R:BuildSync()
		local after = #(R.route or {})
		if after <= 2 then
			return false, ("Build accepted a swapped %d-stop route as current"):format(after)
		end
		return true, ("%d stops, swapped to 2, rebuilt to %d"):format(real, after)
	end)

	check("The router model gives the route back", function()
		-- The planner showed six mounts across five stops after a /mm report --
		-- the harness's own sample, not the plan. RouterModel swaps the plan
		-- for a small sample, and its restoring Build returned EARLY because
		-- the build signature had already been put back, so the signature
		-- claimed a route that R.route no longer held.
		local R = MM.Router
		if not (R and R.Build and MM.RouterModel and MM.RouterModel.Run) then
			return nil, "router model not available"
		end
		local before = routeInHand(R, 2)
		if before < 2 then return nil, "no route to protect" end
		-- A SMALL SAMPLE EXERCISES THE SAME SWAP.
		--
		-- What is being protected here is that the model puts the player's
		-- route back after borrowing the plan. That path does not care how many
		-- goals it borrowed -- but a full run models every profile against every
		-- sampled goal, which measured 1,184 ms in one uninterrupted call, and a
		-- single call is exactly what the client's watchdog kills. Two goals go
		-- through the identical swap, restore and signature handling.
		local ok, run = pcall(MM.RouterModel.Run, 2)
		if not ok then return nil, "model did not run" end
		if type(run) ~= "table" or not run.sample or #run.sample == 0 then
			-- It returned without borrowing anything, so nothing was restored
			-- and there is nothing to conclude. Saying so beats a green tick.
			return nil, "the model found nothing worth sampling, so no swap happened"
		end
		local after = #(R.route or {})
		if after < before then
			return false, ("the model kept the route: %d stops -> %d")
				:format(before, after)
		end
		return true, ("%d stops before the model, %d after (%d goals sampled)")
			:format(before, after, #run.sample)
	end)

	check("What you are standing on is not reordered away", function()
		-- Standing at the Island Expedition NPC -- one minute off, five mounts
		-- on one stop -- the plan opened by flying to Tazavesh. Layer 2
		-- promoted the island correctly and layer 3 moved it to third, because
		-- minimising travel across a hundred stops barely notices where a
		-- zero-travel stop sits. It notices a great deal if you only have time
		-- for two.
		local R = MM.Router
		if not (R and R.route and #R.route > 1) then return nil, "no route" end
		local firstHere, firstOther
		for i, stop in ipairs(R.route) do
			if stop.hereNow and stop.urgency ~= MM.Planner.URGENCY.EXPIRING then
				firstHere = firstHere or i
			elseif stop.urgency ~= MM.Planner.URGENCY.EXPIRING then
				firstOther = firstOther or i
			end
		end
		if not firstHere then return nil, "nothing promoted for being here" end
		if firstOther and firstOther < firstHere then
			return false, ("a stop you must travel to leads at %d, "
				.. "the one you are standing on is at %d"):format(firstOther, firstHere)
		end
		return true, ("standing-here work leads at position %d"):format(firstHere)
	end)

	check("A plan change during a build is not dropped", function()
		-- Clear Plan while a build was in flight was discarded entirely: the
		-- running build finished against the OLD plan, announced itself, and
		-- the planner drew a route for mounts that were no longer planned. The
		-- compact list was right because it reads the plan, not the route.
		local R = MM.Router
		if not (R and R.Build and R.BuildSync and R.IsBuilding) then
			return nil, "no router"
		end
		R:BuildSync()
		local before = #(R.route or {})
		if before < 2 then return nil, "route too short to test" end
		-- Ask twice in a row: the second must not be silently swallowed.
		R.builtSignature, R.builtRouteCount = nil, nil
		R:Build()
		R.builtSignature = "deliberately-stale"
		R:Build()
		local queued = R.rebuildWhenDone
		R:BuildSync()
		if R.IsBuilding() then return false, "still building after a sync build" end
		return true, queued and "second request queued and re-issued"
			or "first build finished before the second arrived"
	end)

	check("Build answers with a status, never a stop count", function()
		-- Start did `local n = R:Build()` and then `if n == 0`, and formatted n
		-- with %d. Build returned NOTHING, so n was nil: the zero-goal guard
		-- could never fire, and the announcement formatted a nil.
		local R = MM.Router
		if not (R and R.Build and R.BUILD_CURRENT) then return nil, "no router" end
		R:BuildSync()
		local status = R:Build()
		if type(status) ~= "string" then
			return false, ("Build returned %s, not a status"):format(type(status))
		end
		local known = { [R.BUILD_CURRENT] = true, [R.BUILD_STARTED] = true,
			[R.BUILD_QUEUED] = true, [R.BUILD_RUNNING] = true,
			[R.BUILD_COMPLETED] = true }
		if not known[status] then return false, "unknown build status: " .. status end
		if status ~= R.BUILD_CURRENT then
			return false, ("a settled route reported '%s', not '%s'")
				:format(status, R.BUILD_CURRENT)
		end
		return true, "status '" .. status .. "' on a settled route"
	end)

	check("BuildSync answers with the number of stops", function()
		-- BuildSync delegated to Build, which returned nothing, so every caller
		-- that wanted a count got nil -- and nil compares equal to nothing.
		local R = MM.Router
		if not (R and R.BuildSync) then return nil, "no router" end
		local n = R:BuildSync()
		if type(n) ~= "number" then
			return false, ("BuildSync returned %s, not a number"):format(type(n))
		end
		if n ~= #R.route then
			return false, ("BuildSync said %d, route holds %d"):format(n, #R.route)
		end
		return true, ("%d stops"):format(n)
	end)

	check("BuildSync finishes a build that was already running", function()
		-- Build returned EARLY when another build was suspended -- including for
		-- a synchronous caller. So BuildSync during a build left the coroutine
		-- suspended and handed back the PREVIOUS route, which is the exact
		-- failure it exists to prevent.
		local R = MM.Router
		if not (R and R.Build and R.BuildSync and R.IsBuilding) then
			return nil, "no router"
		end
		R:BuildSync()
		if #(R.route or {}) < 2 then return nil, "route too short to test" end
		R.builtSignature, R.builtRouteCount, R.chartRank = nil, nil, nil
		R:Build()
		local flying = R.IsBuilding()
		local n = R:BuildSync()
		if R.IsBuilding() then
			return false, "BuildSync returned while a build was still in flight"
		end
		if type(n) ~= "number" or n ~= #R.route then
			return false, "BuildSync did not report the finished route"
		end
		return true, flying and ("drained an in-flight build, %d stops"):format(n)
			or ("build completed within one frame, %d stops"):format(n)
	end)

	check("BuildSync finishes a queued replacement build", function()
		-- A request arriving mid-build is remembered and re-issued. A sync
		-- caller has to drain BOTH, or it measures the build whose inputs are
		-- already known to be out of date.
		local R = MM.Router
		if not (R and R.Build and R.BuildSync and R.IsBuilding) then
			return nil, "no router"
		end
		R:BuildSync()
		if #(R.route or {}) < 2 then return nil, "route too short to test" end
		R.builtSignature, R.builtRouteCount, R.chartRank = nil, nil, nil
		R:Build()
		if not R.IsBuilding() then
			return nil, "the build finished in one frame; nothing to queue behind it"
		end
		-- Force a replacement to be queued behind the running build.
		R:Build(true)
		local queued = R.rebuildWhenDone
		R:BuildSync()
		if R.IsBuilding() then
			return false, "BuildSync returned with a replacement still in flight"
		end
		if R.rebuildWhenDone then
			return false, "a replacement build was left queued after BuildSync"
		end
		return true, queued and "queued replacement drained to completion"
			or "no replacement was needed"
	end)

	check("A finished build announces itself exactly once", function()
		-- MM_ROUTE_ADVANCED meant both "the route finished building" and "the
		-- current goal changed", so nothing could listen for one without being
		-- woken by the other. MM_ROUTE_BUILT is the completion signal, and an
		-- obsolete build -- one whose replacement is already queued -- must not
		-- fire it at all.
		local R = MM.Router
		if not (R and R.BuildSync and R.Build) then return nil, "no router" end
		R:BuildSync()
		local before = watchRouteBuilt()
		R.builtSignature, R.builtRouteCount, R.chartRank = nil, nil, nil
		R:BuildSync()
		local after = watchRouteBuilt()
		if after - before ~= 1 then
			return false, ("one build fired MM_ROUTE_BUILT %d times"):format(after - before)
		end
		-- A cache hit builds nothing, so it must announce nothing.
		R:BuildSync()
		if watchRouteBuilt() ~= after then
			return false, "a cache hit announced a build that never happened"
		end
		return true, "one build, one notification; cache hits stay silent"
	end)

	check("Start refuses an empty plan without activating a route", function()
		-- `if n == 0` never fired, because Build returned nil rather than a
		-- count. An empty plan activated a route with nothing in it.
		local R = MM.Router
		if not (R and R.Start and MM.cdb) then return nil, "no router" end
		local savedPlan, savedActive = MM.cdb.plan, MM.cdb.routeActive
		local savedIndex = MM.cdb.routeIndex
		MM.cdb.plan = {}
		MM.cdb.routeActive = false
		local ok, started = pcall(function() return R:Start() end)
		MM.cdb.plan, MM.cdb.routeActive = savedPlan, savedActive
		MM.cdb.routeIndex = savedIndex
		if not ok then return false, "Start threw on an empty plan: " .. tostring(started) end
		if started ~= false then
			return false, ("Start returned %s on an empty plan"):format(tostring(started))
		end
		return true, "returned false, activated nothing, did not throw"
	end)

	check("The route anchor records a goal that can be found again", function()
		-- SetIndex read `stop.spellID`, and a step has never carried one -- its
		-- mount lives in `entry`. So the anchor was never written, and the
		-- resume-by-identity search that reads it could never match: every
		-- rebuild silently restarted the route at the top.
		local R = MM.Router
		if not (R and R.SetIndex and MM.db) then return nil, "no router" end
		R:BuildSync()
		if #(R.route or {}) < 2 then return nil, "route too short to test" end
		local savedGoal, savedIndex = MM.db.routeGoal, MM.cdb.routeIndex
		R.SetIndex(2)
		local anchor = MM.db.routeGoal
		MM.db.routeGoal, MM.cdb.routeIndex = savedGoal, savedIndex
		if type(anchor) ~= "number" then
			return false, "SetIndex recorded no goal anchor at all"
		end
		local wanted = R.route[2]
		local holds = false
		for _, m in ipairs(wanted.members or { wanted }) do
			if m.entry and m.entry.spellID == anchor then holds = true break end
		end
		if not holds then
			return false, ("anchor %d is not a mount at the stop it anchored")
				:format(anchor)
		end
		return true, ("anchored on spell %d"):format(anchor)
	end)

	check("Optimize orders the plan from the route it just built", function()
		-- Optimize called the ASYNCHRONOUS Build and reordered cdb.plan on the
		-- next line, so it sorted the plan by the previous route every time.
		-- With the route already settled the completion runs inline, so this
		-- asserts the ordering itself rather than the timing.
		local R, P = MM.Router, MM.Planner
		if not (R and P and P.Optimize) then return nil, "no planner" end
		R:BuildSync()
		if #(R.route or {}) < 2 then return nil, "route too short to test" end
		local wanted = {}
		for _, stop in ipairs(R.route) do
			for _, m in ipairs(stop.members or { stop }) do
				if m.entry and m.entry.spellID then wanted[#wanted + 1] = m.entry.spellID end
			end
		end
		local landed = false
		P.SuppressAutoOptimize(function()
			P:Optimize(function() landed = true end)
		end)
		if not landed then
			return nil, "the build did not settle inline; nothing to compare"
		end
		local plan = MM.cdb and MM.cdb.plan or {}
		for i = 1, math.min(#wanted, #plan) do
			if plan[i].spellID ~= wanted[i] then
				return false, ("plan position %d is %s, the route says %s")
					:format(i, tostring(plan[i].spellID), tostring(wanted[i]))
			end
		end
		return true, ("first %d plan entries match the route"):format(math.min(#wanted, #plan))
	end)

	check("Optimize keeps each goal's own added date", function()
		-- Every plan item was restamped with one fresh timestamp on every
		-- optimize, so "when did I plan this" became "when was the route last
		-- charted" -- identical for all of them, rewritten several times an hour.
		local P = MM.Planner
		if not (P and P.Optimize and MM.cdb and MM.cdb.plan) then return nil, "no planner" end
		local plan = MM.cdb.plan
		if #plan < 2 then return nil, "plan too short to test" end
		local was = {}
		for _, item in ipairs(plan) do
			if item.spellID and item.added then was[item.spellID] = item.added end
		end
		local sampled = 0
		for _ in pairs(was) do sampled = sampled + 1 end
		if sampled == 0 then return nil, "no plan entry carries an added date yet" end
		local landed = false
		P.SuppressAutoOptimize(function()
			P:Optimize(function() landed = true end)
		end)
		if not landed then return nil, "the build did not settle inline" end
		local moved, kept = nil, 0
		for _, item in ipairs(MM.cdb.plan) do
			local before = item.spellID and was[item.spellID]
			if before then
				if item.added ~= before then moved = item.spellID else kept = kept + 1 end
			end
		end
		if moved then
			return false, ("spell %d was restamped by an optimize"):format(moved)
		end
		return true, ("%d added dates survived the rewrite"):format(kept)
	end)

	check("Every weight that moves the route is in its cache signature", function()
		-- The signature carried four weights. `era` shifts a goal by half a
		-- tier, `urgency` reorders the bands and `orderCap` decides how far the
		-- clock may overrule layer 1 -- all three changed the route and none
		-- changed the signature, so a chart saved at one setting was restored
		-- at another and the slider read as doing nothing at all.
		--
		-- Asked by swapping the ACCESSOR, not the setting: writing a weight
		-- fires MM_WEIGHTS_CHANGED, which re-charts the plan. Nothing here
		-- should re-chart anything.
		local R, W = MM.Router, MM.Weights
		if not (R and R.Signature and W and W.Get) then return nil, "no router" end
		local base = R.Signature()
		local realGet = W.Get
		local missed = {}
		for _, key in ipairs({ "travel", "effort", "odds", "priority", "era",
			"urgency", "orderCap" }) do
			W.Get = function(k)
				if k == key then return (realGet(k) or 0) + 7 end
				return realGet(k)
			end
			local moved = R.Signature()
			W.Get = realGet
			if moved == base then missed[#missed + 1] = key end
		end
		W.Get = realGet
		if #missed > 0 then
			return false, "invisible to the cache: " .. table.concat(missed, ", ")
		end
		-- The tier order is a setting too, and layer 1 is built from it.
		local realOrder = W.Order
		W.Get = realGet
		W.Order = function()
			local o = realOrder()
			local flipped = {}
			for i = 1, #o do flipped[i] = o[#o - i + 1] end
			return flipped
		end
		local orderMoved = R.Signature()
		W.Order = realOrder
		if orderMoved == base then return false, "tier order is invisible to the cache" end
		return true, "seven weights and the tier order all reach the signature"
	end)

	check("Session length is not part of the base chart", function()
		-- A session is a VIEW applied over the chart, not a different chart.
		-- Folding it into the signature would re-chart the whole plan every
		-- time the dropdown moved.
		local R, W = MM.Router, MM.Weights
		if not (R and R.Signature and W and W.Get) then return nil, "no router" end
		local base = R.Signature()
		local realGet = W.Get
		W.Get = function(k)
			if k == "session" then return (realGet(k) or 0) + 45 end
			return realGet(k)
		end
		local moved = R.Signature()
		W.Get = realGet
		if moved ~= base then
			return false, "session length forces a full re-chart"
		end
		return true, "changing the session length reuses the base chart"
	end)

	check("What you can travel with is in the cache signature", function()
		-- Switching a teleport off fired MM_PLAN_CHANGED, which rebuilt the
		-- route -- and the rebuild took a CACHE HIT, because the signature
		-- could not see teleports at all. The option moved and the route
		-- carried on routing through it.
		local R, TP = MM.Router, MM.Teleports
		if not (R and R.Signature and MM.TravelFingerprint) then return nil, "no router" end
		if not (TP and TP.Options) then return nil, "no teleport layer" end
		local base = R.Signature()
		local realOptions = TP.Options
		-- One fewer option, exactly as switching one off produces.
		TP.Options = function()
			local full, fewer = realOptions() or {}, {}
			for i = 2, #full do fewer[i - 1] = full[i] end
			return fewer
		end
		local moved = R.Signature()
		TP.Options = realOptions
		local restored = R.Signature()
		if #(realOptions() or {}) == 0 then
			return nil, "no teleports available on this character to remove"
		end
		if moved == base then
			return false, "losing a teleport does not change the route signature"
		end
		if restored ~= base then
			return false, "the signature did not settle back when the option returned"
		end
		return true, "a lost teleport changes the signature; regaining it restores it"
	end)

	check("A settled route repaints without re-charting", function()
		-- The cache has to still BE a cache. Everything above adds fields to
		-- the signature, and a field that moves when nothing has changed turns
		-- every repaint into a full chart.
		local R = MM.Router
		if not (R and R.BuildSync and R.Build) then return nil, "no router" end
		R:BuildSync()
		if #(R.route or {}) == 0 then return nil, "nothing routed" end
		local first = R.Signature()
		local status = R:Build()
		local second = R.Signature()
		if first ~= second then
			return false, "the signature is not stable across two reads: " .. tostring(R.cacheWhy)
		end
		if status ~= R.BUILD_CURRENT then
			return false, ("a settled route reported '%s' (%s)")
				:format(tostring(status), tostring(R.cacheWhy))
		end
		return true, "repaint is a cache hit"
	end)

	check("Every currency id measures what the condition names", function()
		-- ID RESOLUTION IS THE EASY HALF and reads 100%. Whether an id measures
		-- the thing its condition NAMES is the half that has been wrong: 3130
		-- was a live id for a previous expansion's delve renown, so last
		-- season's rank satisfied a gate that had never been started.
		--
		-- A disagreement with the client's own name is the only automatic
		-- signal, and it is noisy alone -- conditions are named for the player.
		-- The deliberate ones are recorded with the client name they were
		-- checked against, so a settled disagreement passes and a NEW one, or
		-- a Blizzard rename, does not.
		local IDs = MM.IDs
		if not (IDs and IDs.NameDisagreements) then return nil, "no resolver" end
		local checked, suspect, settled = IDs.NameDisagreements()
		if not checked then return nil, "no currency API" end
		if #suspect > 0 then
			local shown = {}
			for i = 1, math.min(#suspect, 4) do
				local d = suspect[i]
				shown[i] = ("%s: id %d is \"%s\" here, \"%s\" in the client")
					:format(d.name, d.id, d.ours, d.client)
			end
			return false, ("%d id(s) name something else with no record of a check: %s")
				:format(#suspect, table.concat(shown, "; "))
		end
		return true, ("%d currency ids agree, %d differ deliberately and still match")
			:format(checked - #settled, #settled)
	end)

	check("No badge line offers a purchase with no Timewalking week running", function()
		-- Holding the cost is not the same as being able to spend it. The
		-- "you have enough" branch returned before the event check below it
		-- ever ran, so a full purse read as "buy it during the right
		-- Timewalking week!" on a week with no Timewalking at all -- and the
		-- rows carried an "ending soon" tag with nothing to end.
		local TW = MM.Timewalking
		if not (TW and TW.Estimate and TW.IsActive) then return nil, "no timewalking" end
		if TW.IsActive() then
			return nil, "a Timewalking week is running; nothing to assert"
		end
		local offered = {}
		for _, rec in ipairs(MM.DBList or {}) do
			if rec.category == "TIMEWALKING" and rec.conditions then
				local _, _, text = TW.Estimate(rec)
				if text and text:find("buy it", 1, true) then
					offered[#offered + 1] = rec.name
				end
			end
		end
		if #offered > 0 then
			return false, ("%d record(s) say to buy with no event running, e.g. %s")
				:format(#offered, offered[1])
		end
		return true, "no purchase offered outside a Timewalking week"
	end)

	check("Every theme leaves a close button you can actually see", function()
		-- Each theme reaches the close differently: Modern draws its own and
		-- hides the native one, Blizzard restores the template art it never
		-- hid, and ElvUI hides template art and then relies on the native
		-- button anyway. That last combination is how a live but INVISIBLE X
		-- shipped -- clickable, drawn at zero alpha, and visible on neither
		-- of the other two themes. Nothing static sees it, and until now no
		-- gate ran on any theme but the active one.
		local T = MM.Theme
		local frame = _G.MasterMountsFrame
		if not (T and T.ReskinAll and frame) then return nil, "no window" end

		local was = MM.db.theme
		local broken = {}
		local atlasBy = {}
		for _, name in ipairs({ "modern", "blizzard", "elvui" }) do
			MM.db.theme = name
			local ok = pcall(T.ReskinAll)
			if not ok then
				broken[#broken + 1] = name .. " could not be applied"
			else
				-- Whichever button this theme is showing must be the one that
				-- is drawn. A hidden button is fine; a shown one with no art
				-- is the fault.
				local shown
				local candidates = {}
				if frame.mmModernClose then candidates[#candidates + 1] = frame.mmModernClose end
				if frame.mmNativeClose then candidates[#candidates + 1] = frame.mmNativeClose end
				for _, b in ipairs(candidates) do
					if b.IsShown and b:IsShown() then shown = b end
				end
				if not shown then
					broken[#broken + 1] = name .. " shows no close button at all"
				else
					local alpha = shown.GetAlpha and shown:GetAlpha() or 1
					local art = shown.GetNormalTexture
						and select(2, pcall(shown.GetNormalTexture, shown))
					local artAlpha = art and art.GetAlpha and art:GetAlpha() or 1
					if alpha <= 0 then
						broken[#broken + 1] = name .. "'s close button is transparent"
					elseif artAlpha <= 0 then
						broken[#broken + 1] = name .. "'s close button has no visible art"
					end
					-- An atlas names ONE icon inside a shared sheet. Lose it
					-- and the texture still draws, still has alpha, and shows
					-- the entire sheet -- a grid of glyphs where the X was.
					-- Alpha checks cannot see that, so track the atlas itself.
					if art and art.GetAtlas and shown == frame.mmNativeClose then
						atlasBy[name] = select(2, pcall(art.GetAtlas, art)) or false
					end
				end
			end
		end
		MM.db.theme = was
		pcall(T.ReskinAll)

		-- Blizzard leaves the native button untouched, so it is the reference
		-- for what that texture is supposed to be.
		local reference = atlasBy.blizzard
		if reference then
			for name, atlas in pairs(atlasBy) do
				if atlas == false then
					broken[#broken + 1] = name ..
						" dropped the close button's atlas, so it draws the whole sheet"
				end
			end
		end

		if #broken > 0 then
			return false, table.concat(broken, "; ")
		end
		return true, "modern, blizzard and elvui each show a drawn close"
	end)

	check("Native chrome nested inside a container is hidden with it", function()
		-- The window inherits BasicFrameTemplateWithInset, whose sunken inset
		-- keeps its border in a nine-slice CHILD FRAME rather than in its own
		-- regions -- so a collector that stopped at the outer container left
		-- that border drawn: a grey metal rectangle up both sides and along
		-- the bottom of the window, behind the panes.
		--
		-- The probe below carries an Inset and nothing else, which also stands
		-- in for the second half of that fault: the old collector listed its
		-- containers inline, and ipairs over a list with an absent key halts
		-- at the hole, so the Inset was never reached in the first place.
		-- Neither half is visible to a static rule, so assert against a frame.
		local T = MM.Theme
		if not (T and T.HideTemplateArt and T.RestoreTemplateArt) then
			return nil, "no theme seam"
		end
		local probe = CreateFrame("Frame", nil, UIParent)
		probe:Hide()
		probe.Inset = CreateFrame("Frame", nil, probe)
		local own = probe.Inset:CreateTexture(nil, "BACKGROUND")
		probe.Inset.NineSlice = CreateFrame("Frame", nil, probe.Inset)
		local nested = probe.Inset.NineSlice:CreateTexture(nil, "BORDER")
		own:SetAlpha(1)
		nested:SetAlpha(1)

		T.HideTemplateArt(probe)
		if own:GetAlpha() > 0 then
			return false, "the container's own texture was left drawn"
		end
		if nested:GetAlpha() > 0 then
			return false, "a texture one level down was left drawn"
		end

		T.RestoreTemplateArt(probe)
		if nested:GetAlpha() < 1 then
			return false, "the nested texture was not restored, so Blizzard "
				.. "theme would lose its inset border"
		end
		return true, "chrome is collected and restored at both levels"
	end)

	check("A one-use teleport is spent at most once on the route", function()
		-- Journey.Plan could put a teleport in as the FIRST LEG of a multi-leg
		-- trip, and the router wrapped the whole journey as `taxi = true` --
		-- taxis being reusable. So the charge inside it was never consumed, and
		-- the same hearthstone was priced into stop after stop. The route
		-- promised a total that could not be walked.
		local R = MM.Router
		if not (R and R.BuildSync and R.TravelSpends) then return nil, "no router" end
		R:BuildSync()
		if #(R.route or {}) < 2 then return nil, "route too short to test" end
		local claims, worst, worstKey = {}, 0, nil
		for _, stop in ipairs(R.route) do
			for key in pairs(R.TravelSpends(stop) or {}) do
				claims[key] = (claims[key] or 0) + 1
				if claims[key] > worst then worst, worstKey = claims[key], key end
			end
		end
		if worst > 1 then
			return false, ("'%s' is spent %d times in one route"):format(
				tostring(worstKey), worst)
		end
		local spent = 0
		for _ in pairs(claims) do spent = spent + 1 end
		return true, ("%d charge%s spent across %d stops, none twice")
			:format(spent, spent == 1 and "" or "s", #R.route)
	end)

	check("A travel method names the charge it spends", function()
		-- Whether a stop cost a teleport was decided by searching the
		-- human-readable leg description for the word "teleport", and the KEY it
		-- spent was not recoverable at all -- so the route could tell you a
		-- charge had gone without being able to say which one.
		local R = MM.Router
		if not (R and R.ArrivesByTeleport and R.TravelSpends) then
			return nil, "no router"
		end
		R:BuildSync()
		if #(R.route or {}) == 0 then return nil, "nothing routed" end
		local viaTeleport, named, prose = 0, 0, nil
		for _, stop in ipairs(R.route) do
			if R.ArrivesByTeleport(stop) then
				viaTeleport = viaTeleport + 1
				local keys = R.TravelSpends(stop)
				local any = false
				for key in pairs(keys or {}) do
					any = true
					-- A resource key is an option id, never a sentence.
					if type(key) ~= "string" or key:find(" ") then
						prose = tostring(key)
					end
				end
				if any then named = named + 1 end
			end
		end
		if prose then
			return false, "a resource key looks like prose: " .. prose
		end
		if viaTeleport ~= named then
			return false, ("%d stops arrive by teleport, %d can say which")
				:format(viaTeleport, named)
		end
		if viaTeleport == 0 then
			return nil, "no stop on this route arrives by a teleport"
		end
		return true, ("%d teleport arrival%s, every one named"):format(
			viaTeleport, viaTeleport == 1 and "" or "s")
	end)

	check("A session cannot spend one teleport on several stops", function()
		-- Session.Fit called TravelMinutes with no used-resource set at all, so
		-- every candidate was priced from a fresh, unspent toybox: a two-hour
		-- session could hand out the same hearthstone for six different stops
		-- and promise a total nobody could achieve.
		local R, S = MM.Router, MM.Session
		if not (R and S and S.Fit) then return nil, "no session layer" end
		R:BuildSync()
		if #(R.route or {}) < 3 then return nil, "route too short to test" end
		local ok, chosen = pcall(S.Fit, 240)
		if not ok then return false, "Session.Fit threw: " .. tostring(chosen) end
		if type(chosen) ~= "table" or #chosen < 2 then
			return nil, "fewer than two stops fit; nothing to double-spend"
		end
		local claims, worst, worstKey = {}, 0, nil
		for _, pick in ipairs(chosen) do
			for key in pairs(pick.spends or {}) do
				claims[key] = (claims[key] or 0) + 1
				if claims[key] > worst then worst, worstKey = claims[key], key end
			end
		end
		if worst > 1 then
			return false, ("the session spends '%s' %d times"):format(
				tostring(worstKey), worst)
		end
		return true, ("%d stops fitted, no charge spent twice"):format(#chosen)
	end)

	check("Two places in one zone are not given the same journey", function()
		-- Journey.Plan computed with coordinates, direct-flight time and live
		-- teleports, and cached the answer under zone names and map ids alone.
		-- Two stops in one zone shared one answer -- including its ENTRY NODE,
		-- which may be on the far side of the zone from the second one.
		local J = MM.Journey
		if not (J and J.Plan and C_Map and C_Map.GetMapInfo) then
			return nil, "no journey planner"
		end
		local mapID = C_Map.GetBestMapForUnit and C_Map.GetBestMapForUnit("player")
		local info = mapID and C_Map.GetMapInfo(mapID)
		if not (info and info.name) then return nil, "cannot name this map" end
		-- A ZONE OFF THE NETWORK CANNOT ANSWER THIS QUESTION.
		--
		-- Two corners only get different journeys when there is something IN
		-- the zone to travel between. Where a zone has no traversal group, the
		-- planner can only leave it and come back by teleport -- and a teleport
		-- is the same from anywhere, so both corners correctly get one answer.
		--
		-- Reported as itself rather than as a planner defect. Standing in such a
		-- zone used to fail this check with "opposite corners got one identical
		-- answer", which blames the journey planner for missing map data and
		-- blocks a release for a gap that is listed under REMAINING GAPS.
		--
		-- Presence means a travel NODE on this map. MapTraversalGroup is not
		-- that -- it holds 48 zones and Orgrimmar, with 28 nodes, is not one.
		-- ONE NODE IS NOT TWO PLACES. A zone joined by a single portal has one
		-- way in, so both corners share an entry node no matter how far apart
		-- they are; only the legs either side of it can differ. Distinguishing
		-- entry nodes needs at least two, and saying so is not the same as
		-- saying the planner is wrong.
		local nodesHere = 0
		for _, node in pairs(MM.TravelNodes or {}) do
			if tonumber(node.mapID) == mapID then nodesHere = nodesHere + 1 end
		end
		if J.ZoneOnNetwork and not J.ZoneOnNetwork(mapID) then
			return nil, ("%s is not on the travel network, so every journey "
				.. "leaves and returns -- see REMAINING GAPS"):format(info.name)
		end
		-- TWO CORNERS AT DIFFERENT DISTANCES FROM THE DESTINATION.
		--
		-- This asked about (5,5) and (95,95) travelling to (50,50), which are
		-- the same distance from it -- 63.6 either way. Identical answers were
		-- therefore the CORRECT result, and the check only ever passed because
		-- a zone with many nodes gave the two corners different entry nodes.
		-- Standing in a zone with exactly one, the symmetry showed through and
		-- it read as a planner defect.
		--
		-- Off-centre, the near corner is 28 units out and the far one 99. Equal
		-- answers now mean what the check says they mean.
		local aMin, aLegs = J.Plan(info.name, 5, 5, info.name, 25, 25, nil, mapID, mapID)
		local bMin, bLegs = J.Plan(info.name, 95, 95, info.name, 25, 25, nil, mapID, mapID)
		if not (aMin and bMin) then
			return nil, "no route between these points to compare"
		end
		local function entry(legs) return legs and legs[1] and legs[1].to end
		local function opensWithTeleport(legs)
			local mode = legs and legs[1] and legs[1].mode
			return type(mode) == "string" and mode:find("^teleport:") ~= nil
		end
		-- A TELEPORT IS THE SAME FROM ANYWHERE, and that is the point of one.
		--
		-- This check exists because Plan once cached under zone names and map
		-- ids alone, so two stops in one zone shared an answer. Equal totals
		-- only prove that when the winning route DEPENDS on where you started.
		-- Where a teleport out and back beats crossing the zone -- which it
		-- does in a zone joined by a single portal and no flight points --
		-- both corners genuinely take the same journey, and calling that a
		-- collapsed cache blames the planner for being right.
		if aMin == bMin and opensWithTeleport(aLegs) and opensWithTeleport(bLegs) then
			return true, ("both corners take the same teleport (%.2f min); "
				.. "nothing here depends on where you stood"):format(aMin)
		end
		-- With one node the entry is shared by construction, so the TOTALS
		-- carry the whole question: a corner three times further away must
		-- cost more, unless a teleport won -- handled above.
		if nodesHere < 2 then
			if aMin == bMin then
				return false, ("one entry node, both corners cost %.2f min by "
					.. "%s, and one is 3.5x further"):format(aMin,
						tostring(aLegs and aLegs[1] and aLegs[1].mode))
			end
			return true, ("%.2f min from the near corner, %.2f from the far "
				.. "one, through the zone's only node"):format(aMin, bMin)
		end
		if aMin == bMin and entry(aLegs) == entry(bLegs) then
			return false, ("opposite corners got one identical answer (%.2f min via %s)")
				:format(aMin, tostring(entry(aLegs)))
		end
		return true, ("%.2f min from one corner, %.2f from the other")
			:format(aMin, bMin)
	end)

	check("A build publishes one mutually consistent state", function()
		-- The route was swapped atomically; its companions were not. `unrouted`
		-- and `deferred` were wiped at the top of a build and refilled a goal at
		-- a time, and the route went live BEFORE the chart restore, the 2-opt,
		-- the cap, Measure and the session view -- so a late failure could leave
		-- a new order published with totals describing the old one.
		local R = MM.Router
		if not (R and R.BuildSync) then return nil, "no router" end
		R:BuildSync()
		if #(R.route or {}) == 0 then return nil, "nothing routed" end
		if R.builtRouteCount ~= #R.route then
			return false, ("builtRouteCount %s against %d stops")
				:format(tostring(R.builtRouteCount), #R.route)
		end
		if not (R.totals and R.totals.stops == #R.route) then
			return false, ("totals say %s stops, the route holds %d")
				:format(tostring(R.totals and R.totals.stops), #R.route)
		end
		-- Every routed goal indexes to the stop it is actually in.
		local wrong = nil
		for _, stop in ipairs(R.route) do
			for _, m in ipairs(stop.members or { stop }) do
				local id = m.entry and m.entry.spellID
				if id and R.stopBySpell[id] ~= stop then wrong = id end
			end
		end
		if wrong then
			return false, ("spell %d indexes to a stop it is not in"):format(wrong)
		end
		-- An unrouted goal is deliberately ALSO in the route, as a no-location
		-- stop. A deferred one must not be in the route at all.
		local placed = {}
		for _, stop in ipairs(R.route) do
			for _, m in ipairs(stop.members or { stop }) do
				local id = m.entry and m.entry.spellID
				if id then placed[id] = true end
			end
		end
		for _, d in ipairs(R.deferred) do
			if d.entry and placed[d.entry.spellID] then
				return false, (d.entry.name or "a goal") .. " is both routed and held back"
			end
		end
		local idx = MM.cdb and MM.cdb.routeIndex or 1
		if idx < 1 or idx > #R.route then
			return false, ("the route index is %d across %d stops"):format(idx, #R.route)
		end
		return true, ("%d stops, %d unrouted, %d held back, all agreeing")
			:format(#R.route, #R.unrouted, #R.deferred)
	end)

	check("A build that fails late leaves the last route standing", function()
		-- Publication happened before the failure-prone tail, so a throw in
		-- Measure or the preference cap destroyed the last route that worked --
		-- while the build correctly reported failure and fired no completion.
		-- The build now assembles into a stage and publishes once, at the end.
		local R = MM.Router
		if not (R and R.BuildSync and R.Measure) then return nil, "no router" end
		R:BuildSync()
		if #(R.route or {}) < 2 then return nil, "route too short to test" end

		local session = MM.Session and MM.Session.Active and MM.Session.Active()
		local function fingerprint()
			local ids = {}
			for i, stop in ipairs(R.route) do
				ids[i] = tostring(stop.entry and stop.entry.spellID or "?")
			end
			local hd = R.hereDebug
			return table.concat(ids, ",") .. "|" .. #R.unrouted .. "|" .. #R.deferred
				.. "|" .. tostring(R.totals and R.totals.stops)
				.. "|" .. tostring(R.builtRouteCount)
				-- The signature OF THE PUBLISHED ROUTE. It described the route
				-- on screen and was overwritten the instant a build started, so
				-- a failure left it naming a route that was never published.
				.. "|" .. tostring(R.builtSignature)
				-- Identity and content both: a failed build must neither swap
				-- this table nor edit the one already there.
				.. "|" .. tostring(hd) .. ":" .. tostring(hd and hd.promoted)
				.. "," .. tostring(hd and hd.candidates)
				-- The session's promise about the published route.
				.. "|" .. tostring(session and session.planned)
		end
		local before = fingerprint()
		local chartBefore = MM.cdb and MM.cdb.chart
		local hereBefore = R.hereDebug

		-- Measure runs as late as a build gets: after the route is assembled,
		-- the chart restored, the 2-opt run, the cap applied and the pin done.
		--
		-- FORCED rather than clearing the signature: the signature is part of
		-- what is being asserted, so clearing it would be the check moving the
		-- very thing it is about to say has not moved.
		local real = R.Measure
		-- THE ROUTER ANNOUNCES A FAILED BUILD IN CHAT, which is right for a
		-- player and wrong for a check that breaks the build on purpose: the
		-- report printed a red "route build failed" at somebody who had done
		-- nothing but ask for a report.
		--
		-- Captured rather than silenced. The announcement is still proved to
		-- happen -- asserted below -- it just is not shown to a reader who did
		-- not cause it. Silencing it outright would trade one wrong behaviour
		-- for a blind spot.
		local realPrint, said = MM.Print, {}
		-- Packed before the pcall: 5.1 will not let a nested closure reach the
		-- enclosing function's `...`, and the format has to be guarded because
		-- a caller's arguments are not this check's to vouch for.
		MM.Print = function(_, fmt, ...)
			local n, args = select("#", ...), { ... }
			local okFmt, line = pcall(function()
				if n > 0 then return tostring(fmt):format(unpack(args, 1, n)) end
				return tostring(fmt)
			end)
			said[#said + 1] = okFmt and line or tostring(fmt)
		end
		R.Measure = function() error("self-test: deliberate late build failure") end
		local ok = pcall(function() return R:BuildSync(true) end)
		R.Measure = real
		MM.Print = realPrint

		local announced = false
		for _, line in ipairs(said) do
			if line:find("route build failed", 1, true) then announced = true end
		end

		local after = fingerprint()
		local hereAfter = R.hereDebug
		-- CAPTURED BEFORE THE ROUTER IS PUT BACK. The restore below publishes a
		-- fresh chart quite legitimately, so comparing saved variables after it
		-- was measuring the repair rather than the failure.
		local chartAfter = MM.cdb and MM.cdb.chart
		-- Put the router back before reporting, whatever happened above.
		R:BuildSync(true)

		if after ~= before then
			return false, ("a failed build changed the published state\n  was %s\n  now %s")
				:format(before, after)
		end
		if hereAfter ~= hereBefore then
			return false, "a failed build replaced the here-now diagnostic"
		end
		if chartAfter ~= chartBefore then
			return false, "a failed build left a chart behind in saved variables"
		end
		-- Asserted here, after the repair above, so a missing announcement is
		-- reported without leaving the router in the state that was broken on
		-- purpose. Every return past this point has already put it back.
		if not announced then
			return false, "a failed build did not announce itself"
		end
		return true, ok and "the failure was contained and nothing moved"
			or "the build reported failure and nothing moved"
	end)

	check("The published signature describes the published route", function()
		-- One field answered two questions: "what is on screen" and "what is
		-- being built". Starting a build overwrote the first with the second,
		-- so a build that failed or was superseded had to erase it -- throwing
		-- away a true description of a route that was still there and correct.
		local R = MM.Router
		if not (R and R.Signature and R.BuildSync) then return nil, "no router" end
		R:BuildSync()
		if #(R.route or {}) == 0 then return nil, "nothing routed" end
		if R.builtSignature ~= R.Signature() then
			return false, "the published signature is not this plan's signature"
		end
		-- A settled route reports a cache hit, which is only possible when the
		-- published signature genuinely matches.
		if R:Build() ~= R.BUILD_CURRENT then
			return false, ("a settled route reported '%s' (%s)")
				:format(tostring(R:Build()), tostring(R.cacheWhy))
		end
		return true, "the route on screen and its signature agree"
	end)

	check("An unfinished build is not reported as an empty plan", function()
		-- Clear Plan then Auto-Plan All showed "your farm plan is empty" over a
		-- plan of 286 goals. Build is chunked and returns with the work in
		-- flight, so the pane painted the PREVIOUS route -- which Clear Plan
		-- had just emptied. Changing tabs "fixed" it only by re-rendering after
		-- the build landed.
		local R = MM.Router
		if not (R and R.IsBuilding and R.Build) then return nil, "no router" end
		if R.IsBuilding() then return nil, "a build is already running" end
		R.builtSignature, R.chartRank, R.builtRouteCount = nil, nil, nil
		R:Build()
		local flying = R.IsBuilding()
		R:BuildSync()
		if R.IsBuilding() then
			return false, "BuildSync returned with work still in flight"
		end
		-- The signal the view listens on must exist, or nothing repaints when
		-- the route lands and the pane stays wrong until something else redraws.
		if not (MM.Fire and MM.On) then return nil, "no event bus" end
		return true, flying and "build reported in flight, then completed"
			or "build completed within one frame"
	end)

	check("A chunked build never shows a partial route", function()
		-- Chunking was off because RunBuild cleared R.route before refilling
		-- it, so a reader during a build saw an empty list and drew it. The
		-- route is assembled into a local now and swapped in once, after every
		-- yield. This asserts the property directly: pump a build one frame at
		-- a time and check the route is never shorter than it started.
		local R = MM.Router
		if not (R and R.BuildSync and R.Build) then return nil, "no router" end
		R:BuildSync()
		local before = #(R.route or {})
		if before < 3 then return nil, "route too short to test" end
		-- Start a chunked build and look at the route while it is in flight.
		R.builtSignature, R.chartRank, R.builtRouteCount = nil, nil, nil
		R:Build()   -- audit-allow: build-then-read is the assertion here
		local during = #(R.route or {})
		R:BuildSync()
		local after = #(R.route or {})
		if during == 0 then
			return false, "the route was EMPTY while a build was in flight"
		end
		if during < before then
			return false, ("partial route visible mid-build: %d of %d")
				:format(during, before)
		end
		return true, ("%d stops before, %d visible mid-build, %d after")
			:format(before, during, after)
	end)

	check("The route builds cleanly", function()
		-- A one-line omission broke every route build in the addon and 57 checks
		-- did not notice, because every one of them tested a PIECE. Nothing ran
		-- the pipeline. This does, and it is the check that should have existed
		-- first.
		local R = MM.Router
		local ok, err = pcall(function() return R:BuildSync() end)
		if not ok then return false, "Router:Build() threw: " .. tostring(err) end

		local planned = #MM.Planner:GetPlan()
		if planned == 0 then return nil, "plan is empty — nothing to build" end

		-- every planned goal has to land somewhere: routed, unrouted or parked
		local seen, twice = {}, nil
		for _, stop in ipairs(R.route) do
			for _, m in ipairs(stop.members or { stop }) do
				if m.entry then
					if seen[m.entry] then twice = m.entry.name end
					seen[m.entry] = true
				end
			end
		end
		for _, e in ipairs(R.unrouted) do seen[e] = true end
		for _, d in ipairs(R.deferred) do if d.entry then seen[d.entry] = true end end
		if twice then return false, twice .. " was routed twice" end

		local lost = 0
		for _, e in ipairs(MM.Planner:GetPlan()) do if not seen[e] then lost = lost + 1 end end
		if lost > 0 then
			return false, ("%d of %d planned goals vanished from the route"):format(lost, planned)
		end
		if #R.route == 0 and #R.unrouted == 0 then
			return false, ("%d goals planned and nothing routed"):format(planned)
		end
		return true, ("%d stops, %d parked, all %d goals accounted for")
			:format(#R.route, #R.deferred, planned)
	end)

	check("Building the route is fast", function()
		-- A route build froze the client for MINUTES and the addon had no way to
		-- say so: the teleport option list was being re-read, with its ten-odd C
		-- calls per option, inside the greedy's inner loop. Nothing measured
		-- anything, so the only symptom was a player waiting.
		local R = MM.Router
		if not debugprofilestop then return nil, "no timer on this client" end
		-- TIME A REAL BUILD, NOT A CACHE HIT.
		--
		-- This reported "0 ms for 286 goals" in the same run where the route
		-- section said "built in 1483 ms -- too slow, this is a freeze". Both
		-- measured the same function. Build returns early when builtSignature
		-- still matches the plan, and by the time the checks run it always
		-- does, so the check that exists to catch the freeze was timing a
		-- return statement and passing on it.
		--
		-- Clearing the signature first is what every other honest caller of
		-- Build has had to learn to do.
		local savedSig, savedRank = R.builtSignature, R.chartRank
		R.builtSignature, R.chartRank = nil, nil
		local before = debugprofilestop()
		R:BuildSync()
		local ms = debugprofilestop() - before
		R.builtSignature, R.chartRank = savedSig, savedRank
		local goals = #MM.Planner:GetPlan()
		if ms > 400 then
			return false, ("%.0f ms for %d goals — that is a visible freeze"):format(ms, goals)
		end
		if ms > 150 then
			return nil, ("%.0f ms for %d goals — watch this"):format(ms, goals)
		end
		return true, ("%.0f ms for %d goals"):format(ms, goals)
	end)

	check("Expansion skill lines are read at all", function()
		-- The wormhole gates now depend entirely on this. If the API returns
		-- nothing, every skill-line gate refuses and the options simply never
		-- appear -- a silent, invisible failure, which is the worst kind. Report
		-- it rather than let it hide.
		local C = MM.Conditions
		if not (C and C.SkillLines) then return false, "skill-line lookup missing" end
		if not (C_TradeSkillUI and C_TradeSkillUI.GetAllProfessionTradeSkillLines) then
			return nil, "this client has no trade-skill line API — gates will refuse"
		end
		local lines = C.SkillLines()
		if #lines == 0 then
			return false, "the trade-skill API returned nothing at all"
		end
		-- The catalogue proves the API works; what is LEVELLED is what gates
		-- anything. A character with no professions is legitimate, so that is a
		-- note rather than a failure.
		local learned = C.LearnedSkillLines()
		if #learned == 0 then
			-- NOTHING LEVELLED IS ONLY LEGITIMATE IF THERE IS NOTHING TO LEVEL.
			--
			-- This degraded either way, on the reasoning that a character with
			-- no professions is legitimate -- true, and it made the check blind
			-- to the case it was written for. A maxed engineer reported "none
			-- levelled" and six engineering travel options were withheld, while
			-- the report called that correct.
			--
			-- GetProfessions is the reader that demonstrably works. When it
			-- names professions and every skill line still reads zero, the two
			-- disagree, and that is a defect rather than an absent feature.
			local known = C.KnownProfessions and C.KnownProfessions() or {}
			if #known > 0 then
				-- WHETHER IT COSTS ANYTHING, not merely whether it is missing.
				--
				-- The client will not report expansion skill levels here, and
				-- no amount of code makes it. What can be asked is whether that
				-- silence is still WITHHOLDING something: an option refused
				-- because a level could not be read is a capability taken away
				-- from a character who may have earned it, and that is a
				-- failure. The same silence, routed around by asking the client
				-- whether the item is usable, costs nothing and is a platform
				-- limit like any other.
				local blocked = {}
				local TP = MM.Teleports
				if TP and TP.Gates then
					for _, o in ipairs(TP.Gates()) do
						if not o.usable and type(o.why) == "string"
							and o.why:find("not readable", 1, true) then
							blocked[#blocked + 1] = o.name
						end
					end
				end
				local names = {}
				for _, p in ipairs(known) do
					names[#names + 1] = ("%s %d"):format(p.name, p.level or 0)
				end
				if #blocked > 0 then
					return false, ("%d travel option(s) refused because a skill "
						.. "level could not be read -- %s -- on a character "
						.. "GetProfessions reports as %s"):format(#blocked,
							table.concat(blocked, ", "), table.concat(names, ", "))
				end
				return nil, ("all %d skill lines read 0 while GetProfessions "
					.. "reports %s -- the level is unreadable here, and nothing "
					.. "is being withheld for it"):format(#lines,
						table.concat(names, ", "))
			end
			return nil, ("%d skill lines in the catalogue, and GetProfessions "
				.. "names none either -- both readers agree"):format(#lines)
		end
		-- every line must carry a usable level, or comparisons are meaningless
		for _, line in ipairs(lines) do
			if type(line.level) ~= "number" then
				return false, (line.label or "?") .. " has no numeric skill level"
			end
		end
		return true, ("%d levelled of %d in the catalogue"):format(#learned, #lines)
	end)

	check("Skill-line gates are exact", function()
		-- Two independent traps, both real: expansion lines must not satisfy each
		-- other, and the required LEVEL is not always 1 (Northrend Engineering
		-- needs 40, Classic 260, Outland 50).
		local C = MM.Conditions
		if not (C and C.SkillLineLevel) then return false, "skill-line lookup missing" end
		-- a name that cannot exist must never match anything
		if C.SkillLineLevel("Nonexistent Zzz Engineering") ~= nil then
			return false, "a nonsense skill line matched something"
		end
		return true, "unknown skill lines refuse rather than guess"
	end)

	check("Achievement kind comes from the client", function()
		-- PvP and guild achievements are never soloable, and the client knows
		-- which are which. Reading that beats inferring it from our own prose,
		-- which is what we were reduced to before.
		local C = MM.Conditions
		if not C.AchievementClass then return false, "classifier missing" end
		if not (GetAchievementCategory and GetCategoryInfo) then
			return nil, "this client has no achievement category API"
		end
		-- 2336 is "Stormwind City" exploration; any stable id proves the walk
		-- works. What matters is that a known id yields a path and an unknown
		-- one yields nothing rather than a guess.
		if C.AchievementClass(99999999) ~= nil then
			return false, "an impossible achievement id was classified anyway"
		end
		local classified, pvp = 0, 0
		for _, rec in pairs(MM.DBByName) do
			if rec.category == "ACHIEVEMENT" then
				local id = C.RecordAchievementID(rec)
				local class = id and C.AchievementClass(id)
				if class then
					classified = classified + 1
					if class.pvp or class.guild then pvp = pvp + 1 end
					if type(class.text) ~= "string" or class.text == "" then
						return false, rec.name .. " classified with no category path"
					end
				end
			end
		end
		if classified == 0 then
			return nil, "no achievement record names an id yet — nothing to classify"
		end
		return true, ("%d classified from the client, %d PvP or guild"):format(classified, pvp)
	end)

	check("The clock cannot drag a goal too far", function()
		-- The matrix reported for weeks: "Layer 1 works; layer 3 overrides it."
		-- Routing is the final adjudicator by design, but two continent hops
		-- dwarf any preference multiplier, so geography won nearly every tie
		-- and goals arrived 57 places ahead of where preference put them.
		--
		-- The cap is the middle ground the chosen middle ground is: the clock stays free inside
		-- a band, and cannot pull anything outside it. This checks the promise
		-- the slider makes, using the router's own measurement rather than
		-- recomputing it here -- a check that re-derives the answer agrees with
		-- itself precisely when it is least useful.
		local R = MM.Router
		if not (R and R.ApplyPreferenceCap and R.PreferenceCap) then
			return false, "preference cap missing"
		end
		local cap = R.PreferenceCap()
		if not cap then return nil, "no cap set (orderCap 0 — the clock is unbound)" end
		local rep = R.capReport
		-- NOTHING TO CAP IS NOT A BROKEN CAP.
		--
		-- capReport only exists once a route has been ordered, and an empty
		-- plan never orders one. This called that a FAILURE and held the
		-- release gate shut over a plan the player had simply cleared -- while
		-- its own sibling two lines down passed vacuously on the same state,
		-- reporting "0 -> 0 min, worst displacement -1".
		if not rep then
			if #(MM.cdb and MM.cdb.plan or {}) == 0 then
				return nil, "plan is empty -- nothing to cap"
			end
			return false, "the cap ran but reported nothing"
		end
		if rep.worst > cap then
			return false, ("a goal sits %d places from its preference rank, cap is %d")
				:format(rep.worst, cap)
		end
		return true, ("cap %d places; worst displacement %d across %d stops, %d moved")
			:format(cap, rep.worst, rep.stops, rep.shifted)
	end)

	check("The cap costs travel, and says how much", function()
		-- Constraining the order must cost SOMETHING in travel or it is not
		-- doing anything. This measures the trade rather than asserting it is
		-- free, because "free" would mean the cap never binds.
		local R = MM.Router
		if not (R and R.RouteMinutes) then return false, "router missing" end
		-- A VACUOUS PASS IS WORSE THAN A WARN.
		--
		-- On an empty plan this reported "cap 8 costs 0.0% more travel
		-- (0 -> 0 min), worst displacement -1" and went green -- measuring
		-- nothing and calling it evidence, in the same run where its sibling
		-- failed the release gate over the identical state. Zero minutes
		-- against zero minutes says nothing about whether the cap binds.
		if #(MM.cdb and MM.cdb.plan or {}) == 0 then
			return nil, "plan is empty -- no travel to trade against"
		end
		local saved = MM.db.weights
		local function build(capValue)
			local w = { schema = 2 }
			for k, v in pairs(saved or {}) do
				if k ~= "order" then w[k] = v end
			end
			if saved and saved.order then
				local o = {}
				for i, t in ipairs(saved.order) do o[i] = t end
				w.order = o
			end
			w.orderCap = capValue
			MM.db.weights = w
			MM:Fire("MM_WEIGHTS_CHANGED")
			R:BuildSync()
			return R.totals and R.totals.minutes or 0, R.capReport
		end
		local free = build(0)
		local capped, rep = build(8)
		MM.db.weights = saved
		MM:Fire("MM_WEIGHTS_CHANGED")
		-- INVALIDATE, DO NOT REBUILD. Putting the weights back needs the next
		-- build to use them, not a chart right now -- and this check already
		-- pays for two full searches. The third was a third of its cost for
		-- nothing anybody was waiting on.
		R.Invalidate()
		if capped < free * 0.999 then
			return false, ("capping made the route FASTER (%d vs %d) — the cap is "
				.. "not constraining anything"):format(capped, free)
		end
		local pct = free > 0 and ((capped - free) / free * 100) or 0
		return true, ("cap 8 costs %.1f%% more travel (%d -> %d min), worst "
			.. "displacement %d"):format(pct, math.floor(free), math.floor(capped),
			rep and rep.worst or -1)
	end)

	check("A plan edit does not re-chart on the frame", function()
		-- Editing the plan while a route ran called BuildSync purely so the
		-- next line could ask whether the route still existed -- a full
		-- re-chart, about 1.6 s, on every click of [+] or [-]. The question is
		-- about plan membership and the plan can just be read; the optimised
		-- order now lands asynchronously.
		local R = MM.Router
		if not (R and R.DropUnplanned and R.BuildSync) then return nil, "no router" end
		R:BuildSync()
		if #(R.route or {}) < 2 then return nil, "route too short to test" end

		-- Trimming to the plan the route was built from must change nothing:
		-- an edit that adds a goal must not disturb the visible route at all.
		local planned = {}
		for _, item in ipairs(MM.cdb and MM.cdb.plan or {}) do
			if item.spellID then planned[item.spellID] = true end
		end
		local before = #R.route
		local goals, stops = R.DropUnplanned(planned)
		if goals ~= 0 or stops ~= 0 then
			return false, ("trimming to the current plan removed %d goal(s) in %d stop(s)")
				:format(goals, stops)
		end
		if #R.route ~= before then
			return false, "trimming to the current plan changed the route"
		end

		-- And the recorded cost of the last real edit, if there has been one.
		local m = R.lastPlanEditMs
		if m and m.build and m.build > 250 then
			return false, ("a plan edit still spent %.0f ms on the frame"):format(m.build)
		end
		return true, m and ("last edit cost %.0f ms on the frame, %d goal(s) removed")
			:format(m.build or 0, m.dropped or 0)
			or "no plan edit measured yet this session"
	end)

	check("The origin search is cached for more than one place", function()
		-- "From where I stand, how far is everything" is one search over the
		-- whole travel graph. It was cached for a SINGLE origin, so anything
		-- walking a chain -- the route builder, the session fitter -- evicted
		-- the previous answer at every step and paid for it again.
		local J = MM.Journey
		if not (J and J.fromStats) then return nil, "no journey stats" end
		local s = J.fromStats
		local asked = (s.hits or 0) + (s.misses or 0)
		if asked == 0 then return nil, "nothing has asked for a travel origin yet" end
		if (s.searches or 0) > asked then
			return false, ("%d searches for %d questions"):format(s.searches, asked)
		end
		-- The bound has to actually bound. Evictions above the number of
		-- searches would mean it is thrashing rather than caching.
		if (s.evictions or 0) > (s.searches or 0) then
			return false, ("%d evictions against %d searches — the cache is thrashing")
				:format(s.evictions, s.searches)
		end
		return true, ("%d searches, %d of %d questions reused, %d evicted")
			:format(s.searches or 0, s.hits or 0, asked, s.evictions or 0)
	end)

	check("The minimap driver stops ticking when there is nothing to draw", function()
		-- An OnUpdate is the one shape that is expensive by default and
		-- invisible by default: it fires every frame whether or not there is
		-- anything to do. This one ran for the whole session once installed,
		-- including in zones with nothing indexed at all.
		local MP = MM.MapPins
		if not (MP and MP.driverStats) then return nil, "the pin layer is not loaded" end
		local ds = MP.driverStats
		if ds.ticks == 0 then return nil, "the driver has not run yet" end
		-- A full pass every second and moves at 20 Hz: moves should dominate
		-- fulls whenever pins are actually up, and neither may exceed ticks.
		if ds.fulls > ds.ticks or ds.moves > ds.ticks then
			return false, ("%d full and %d move against %d tick(s)")
				:format(ds.fulls, ds.moves, ds.ticks)
		end
		return true, ("%d tick(s), %d full, %d move, %d park(s), %.0f ms%s")
			:format(ds.ticks, ds.fulls, ds.moves, ds.parked, ds.ms,
				MP.driverParked and (" — parked: " .. MP.driverParked) or "")
	end)

	check("The plan tooltip's travel figure is the travel figure", function()
		-- "Of which travel" showed `minutes - routeMinutes`. Those two totals do
		-- not differ by travel -- both contain all of it. `minutes` counts every
		-- attempt a mount is expected to need; `routeMinutes` counts travel plus
		-- ONE visit each, so the remainder is the grind. A plan of long farms
		-- reported nearly all its time as flying, to a collector using exactly
		-- that number to decide whether an evening was worth it.
		--
		-- SYNTHETIC TOTALS, chosen so the right answer and the wrong one cannot
		-- coincide: travel is 10, the grind remainder is 100, and neither equals
		-- any other figure in the table. No route, no build, no window.
		local UI = MM.UI
		if not (UI and UI.PlanTimeRows) then return nil, "planner rows not available" end
		local totals = {
			stops = 4,
			travelMinutes = 10,
			routeMinutes = 70,     -- travel 10 + one visit each
			minutes = 170,         -- travel 10 + the whole grind
			mounts = 1.5,
		}
		local rows = UI.PlanTimeRows(totals)
		local byLabel = {}
		for _, row in ipairs(rows) do byLabel[row[1]] = row[2] end

		local wantTravel = U.FormatSeconds(totals.travelMinutes * 60)
		local wantFarming = U.FormatSeconds(
			(totals.minutes - totals.routeMinutes) * 60)
		if wantTravel == wantFarming then
			return nil, "the fixture cannot tell the two figures apart"
		end
		if byLabel["Of which travel"] ~= wantTravel then
			local looksOld = byLabel["Of which travel"] == wantFarming
			return false, ("travel row is %s, travelMinutes is %s%s")
				:format(tostring(byLabel["Of which travel"]), wantTravel,
					looksOld and " — it is showing repeated farming" or "")
		end
		if byLabel["Repeated farming"] ~= wantFarming then
			return false, ("farming row is %s, expected %s")
				:format(tostring(byLabel["Repeated farming"]), wantFarming)
		end
		-- The two rows must not be the same number, or neither assertion means
		-- anything on a route where they happen to agree.
		if byLabel["Of which travel"] == byLabel["Repeated farming"] then
			return false, "travel and farming are reporting the same figure"
		end
		-- Travel below a minute is omitted rather than shown as nothing.
		local quiet = UI.PlanTimeRows({ stops = 2, travelMinutes = 0.2,
			routeMinutes = 30, minutes = 30, mounts = 0 })
		for _, row in ipairs(quiet) do
			if row[1] == "Of which travel" then
				return false, "a travel figure under a minute was still shown"
			end
		end
		return true, ("travel %s, farming %s, from separate totals")
			:format(byLabel["Of which travel"], byLabel["Repeated farming"])
	end)

	check("One kill counts for every mount that source owes", function()
		-- The watch list was `[npcID] = spellID`, so the last planned record to
		-- mention an npc overwrote every earlier one. Twelve ids in the
		-- database carry more than one kill-source record -- Coren Direbrew has
		-- both Brewfest mounts, three ids carry three each -- and killing one
		-- of those moved a single mount's count and silently left the rest.
		--
		-- SENTINEL SPELL IDS, and the tally is put back afterwards. A check
		-- that inflates real attempt counts corrupts the very statistic it
		-- exists to protect.
		local A = MM.Attempts
		if not (A and A.RecordMany and A.ForgetSources) then
			return nil, "batch recording not available"
		end
		local a, b = -9001, -9002
		local acct = MM.db.attempts or {}
		local wasA, wasB = acct[a], acct[b]
		-- EVERY STORE Record TOUCHES, not just the counter.
		--
		-- Record also stamps MM.db.attemptTimes, and this check restored the
		-- tally but not the timestamps -- so a self-test left two sentinel
		-- entries behind in the player's saved variables, growing by two on
		-- every run. A check that measures the statistics must not be able to
		-- change them.
		--
		-- Captured by VALUE and restored by assignment, so a key that was
		-- absent goes back to absent rather than to an empty table.
		local times = MM.db.attemptTimes
		local wasTimeA = times and times[a]
		local wasTimeB = times and times[b]
		local hadTimes = times ~= nil
		local restore = function()
			acct[a], acct[b] = wasA, wasB
			if MM.cdb and MM.cdb.attempts then
				MM.cdb.attempts[a], MM.cdb.attempts[b] = nil, nil
			end
			if MM.db.attemptTimes then
				MM.db.attemptTimes[a] = wasTimeA
				MM.db.attemptTimes[b] = wasTimeB
				-- Record creates the table on first use. If it did not exist
				-- before this check ran, it must not exist after.
				if not hadTimes then MM.db.attemptTimes = nil end
			end
			A.ForgetSources()
		end
		A.ForgetSources()
		-- The tokens say what these attempts CONCERN, which is how the two
		-- signals for one kill find each other.
		local link = { "npc:-9000", "spell:" .. a, "spell:" .. b }
		local ok, err = pcall(function()
			local both = A.RecordMany({ a, b }, "selftest:corpse-1", "loot", link)
			if both ~= 2 then
				error(("one source paid %d of 2 mounts"):format(both), 0)
			end
			-- The same corpse opened again is not a second attempt.
			if A.RecordMany({ a, b }, "selftest:corpse-1", "loot", link) ~= 0 then
				error("re-opening one corpse counted again", 0)
			end
			-- The same kill arriving down the other pipe is not either -- it
			-- adopts the source the loot already paid under.
			if A.RecordMany({ a, b }, "selftest:encounter-1", "kill", link) ~= 0 then
				error("the encounter signal counted a kill the loot already had", 0)
			end
			-- A DIFFERENT corpse of the same creature still is. Two loots are
			-- two attempts however alike they look.
			if A.RecordMany({ a, b }, "selftest:corpse-2", "loot", link) ~= 2 then
				error("a second corpse did not count", 0)
			end
		end)
		restore()
		if not ok then return false, tostring(err) end
		return true, "one source, one attempt per mount, across both signals"
	end)

	check("The attempt watch list is a set, not a single mount", function()
		-- A count of shared sources is the trace that was missing: without it,
		-- "0 attempts" on one Brewfest mount beside a healthy number on the
		-- other reads as bad luck rather than as a bug.
		local S = MM.Scanner
		if not (S and S.WatchedCounts) then return nil, "scanner counts missing" end
		local npcs, watched, shared = S.WatchedCounts()
		if type(npcs) ~= "number" or type(watched) ~= "number"
			or type(shared) ~= "number" then
			return false, "watch counts are not numbers"
		end
		if watched < npcs then
			return false, ("%d npcs watched but only %d mounts claimed")
				:format(npcs, watched)
		end
		if shared > npcs then
			return false, ("%d shared npcs out of %d"):format(shared, npcs)
		end
		-- Every watched mount must be planned and still missing.
		-- COLLECTED MOUNTS ARE NOT WATCHED, so they must not be counted here
		-- either. This counted every planned kill-source goal including the ones
		-- already owned, and then called the watch list short by exactly those.
		local planned = 0
		for _, item in ipairs(MM.cdb and MM.cdb.plan or {}) do
			local entry = S.bySpell and S.bySpell[item.spellID]
			local rec = entry and entry.rec
			if rec and rec.npc and rec.npc.id and MM.KILL_BASED[rec.category]
				and not entry.collected then
				planned = planned + 1
			end
		end
		if watched ~= planned then
			return false, ("%d kill-source goals planned, %d watched")
				:format(planned, watched)
		end
		return true, ("%d npcs, %d mounts, %d source%s owing more than one")
			:format(npcs, watched, shared, shared == 1 and "" or "s")
	end)

	check("Attempts are account-wide and never imply a pity timer", function()
		-- Mounts are account-wide, so attempts must be. They were counted per
		-- character, which made thirty kills on a main and twenty on an alt
		-- read as thirty -- incoherent for an addon that tells you which
		-- character to do a thing on.
		--
		-- And the framing matters as much as the count. Drops are memoryless:
		-- a long streak changes nothing about the next attempt. An addon that
		-- implies otherwise teaches people to chase a pity timer that does not
		-- exist, which makes their decisions worse, not better.
		local A = MM.Attempts
		if not (A and A.Get and A.Line) then return false, "attempts module missing" end
		if MM.db.attempts == nil then
			return false, "attempts are not stored on the account"
		end
		-- The statistic must be the real one: P(nothing after n) = (1-p)^n.
		local probe = { name = "probe", dropRate = 2 }
		local still = A.Unluckiness(probe, 100)
		if not still or math.abs(still - 0.98 ^ 100) > 1e-9 then
			return false, "unluckiness is not (1-p)^n"
		end
		-- No drop rate must mean no claim invented.
		if A.Unluckiness({ name = "x" }, 50) ~= nil then
			return false, "invented a statistic for a mount with no rate"
		end
		local total, mounts = 0, 0
		for _, n in pairs(MM.db.attempts) do total = total + n; mounts = mounts + 1 end
		local merged = 0
		for _ in pairs(MM.db.attemptsMerged or {}) do merged = merged + 1 end
		return true, ("%d attempts across %d mounts, %d character%s folded in")
			:format(total, mounts, merged, merged == 1 and "" or "s")
	end)

	check("An ambiguous zone name resolves to a map we can route to", function()
		-- "The Forbidden Reach" is four maps. The band-and-kind scorer picked
		-- one C_Map cannot turn into world coordinates, so two goals reached
		-- the route with no arrow. Handled correctly -- but avoidable, because
		-- a sibling map with the same name CAN be positioned.
		--
		-- Checked against the LIVE client because it is entirely about what
		-- this client's map set can do; a fixture would only test my model of
		-- it, which is the assumption that produced the bug.
		local U2 = MM.Util
		if not (U2 and U2.ResolveMapForRecord and U2.GetWorldPos) then
			return nil, "map helpers unavailable"
		end
		local checked, unpositionable, worst = 0, 0, nil
		local preferredBadly, badExample = 0, nil
		for _, rec in pairs(MM.DBByName) do
			local zone = rec.zone and rec.zone.name
			if zone and rec.obtainable then
				local mapID = U2.ResolveMapForRecord(zone, rec)
				if mapID then
					checked = checked + 1
					local _, world = U2.GetWorldPos(mapID, 50, 50)
					if not world then
						unpositionable = unpositionable + 1
						worst = worst or ("%s -> map %d"):format(zone, mapID)
						-- Was a same-named sibling positionable? If so we chose
						-- the worse of two, which is the actual defect.
						if U2.ResolveMapsByName then
							for _, sib in ipairs(U2.ResolveMapsByName(zone) or {}) do
								if sib ~= mapID then
									local _, sw = U2.GetWorldPos(sib, 50, 50)
									if sw then
										preferredBadly = preferredBadly + 1
										badExample = badExample or ("%s picked map %d, but sibling %d can be positioned")
											:format(zone, mapID, sib)
										break
									end
								end
							end
						end
					end
				end
			end
		end
		if preferredBadly > 0 then
			return false, ("%d zone(s) chose an unpositionable map over a "
				.. "positionable sibling — e.g. %s"):format(preferredBadly, badExample)
		end
		if checked == 0 then return nil, "no zones resolved yet" end
		-- THE RULE WAS STATED AND NOT TESTED.
		--
		-- "We never PREFER an unpositionable map when a sibling works" was
		-- written in this comment and then never checked -- the body counted
		-- and returned true whatever it counted, so the exact regression it
		-- exists to catch would have passed. It is asked now.
		return true, ("%d resolved zones, %d have no world position%s")
			:format(checked, unpositionable,
				worst and (" (e.g. " .. worst .. ")") or "")
	end)

	check("Outland and Draenor share three zone names, and are told apart", function()
		-- Warlords rebuilt Draenor using names Outland already had: Nagrand is
		-- map 107 AND 550, Shadowmoon Valley 104 AND 539, Shattrath City 111 AND
		-- 594. Picking the wrong copy does not misplace a goal by a few yards,
		-- it sends someone to a different world.
		--
		-- SIMULATED, BECAUSE IT CANNOT BE ASKED FOR. Checking this by hand needs
		-- the mount set as a goal, and a mount already collected can never be a
		-- goal. GetRecordMapID is what the router and the arrow call.
		--
		-- NO RULE IS APPLIED HERE, DELIBERATELY. Two earlier versions of this
		-- check each invented one and each was wrong: a name with several maps
		-- is not a name with several continents, and an expansion does not
		-- confine its mounts to its own continent. The expansion rule that
		-- replaced them is wrong too -- Amani Hunting Bear is a Dragonflight
		-- record whose vendor stands in Outland -- which is the whole reason
		-- these are written down one by one rather than derived.
		--
		-- So the question asked is only: did the record say, and did the
		-- resolver agree? A new mount in one of these zones with no line in the
		-- pinned file fails here, rather than silently inheriting whichever copy
		-- the name resolver happened to prefer.
		local U2 = MM.Util
		if not (U2 and U2.GetRecordMapID) then return nil, "map helpers unavailable" end
		local TWINS = { ["Nagrand"] = true, ["Shadowmoon Valley"] = true,
			["Shattrath City"] = true }
		local checked, unpinned, disagreed, example = 0, 0, 0, nil
		for _, rec in pairs(MM.DBByName) do
			local zn = rec.zone and rec.zone.name
			if zn and TWINS[zn] then
				checked = checked + 1
				local pinned = rec.zone.mapID
				if not pinned then
					unpinned = unpinned + 1
					example = example or ("%s (%s) states no map"):format(rec.name, zn)
				else
					local got = U2.GetRecordMapID(rec)
					if got ~= pinned then
						disagreed = disagreed + 1
						example = example or ("%s (%s) states map %d, resolver chose %s")
							:format(rec.name, zn, pinned, tostring(got))
					end
				end
			end
		end
		if checked == 0 then return nil, "no record names a twinned zone" end
		if unpinned > 0 or disagreed > 0 then
			return false, ("%d of %d twinned-zone goal(s) unresolved: %d state no "
				.. "map, %d disagree with the resolver — e.g. %s")
				:format(unpinned + disagreed, checked, unpinned, disagreed, example)
		end
		return true, ("%d goal(s) name a zone that exists on two continents, "
			.. "every one pinned to a stated map"):format(checked)
	end)

	check("Source comparison scores real disagreement", function()
		-- The audit is only useful if its score means something, so prove both
		-- ends against text whose answer is known: identical acquisitions in
		-- different words must score high, and a genuinely wrong record low.
		local SA = MM.SourceAudit
		if not (SA and SA.Compare) then return nil, "source audit unavailable" end
		local same = SA.Compare("Drops from So'leah in Tazavesh, the Veiled Market.",
			"Drops from So'leah in Tazavesh, the Veiled Market (Mythic)")
		local wrong = SA.Compare("Drops from So'leah in Tazavesh, the Veiled Market.",
			"Purchased from a vendor in Dornogal for gold")
		if not (same and wrong) then return false, "comparison returned nil" end
		if same <= wrong then
			return false, ("same-source %.2f did not beat wrong-source %.2f")
				:format(same, wrong)
		end
		if same < 0.9 then
			return false, ("a reworded match scored only %.2f"):format(same)
		end
		if wrong > 0.4 then
			return false, ("a wrong source still scored %.2f"):format(wrong)
		end
		return true, ("reworded match %.0f%%, wrong source %.0f%%")
			:format(same * 100, wrong * 100)
	end)

	check("The route lifecycle owns its windows", function()
		-- Starting a route is a mode change: the plan HUD, arrow and Next Up
		-- come up together and the full window steps aside; stopping takes the
		-- same three away. Checks the wiring exists rather than driving the
		-- real frames, which a headless self-test cannot see.
		local need = {
			["UI.HideMain"]         = MM.UI and MM.UI.HideMain,
			["UI.SetCompactShown"]  = MM.UI and MM.UI.SetCompactShown,
			["UI.ShowMonitor"]      = MM.UI and MM.UI.ShowMonitor,
			["UI.HideMonitor"]      = MM.UI and MM.UI.HideMonitor,
			["Theme.CreateStopButton"] = MM.Theme and MM.Theme.CreateStopButton,
		}
		local missing = {}
		for name, fn in pairs(need) do
			if type(fn) ~= "function" then missing[#missing + 1] = name end
		end
		if #missing > 0 then
			return false, "missing: " .. table.concat(missing, ", ")
		end
		-- The zone window must NOT be part of it: it answers a question that
		-- has nothing to do with whether a route is running.
		if MM.ZoneAlert and MM.ZoneAlert.boundToRoute then
			return false, "the zone window was tied to the route lifecycle"
		end
		return true, "5 lifecycle hooks present; zone window independent"
	end)

	check("Weights changes always reach the plan", function()
		-- This used to refuse to re-optimize while a route was running and tell
		-- the player to stop the route, press Optimize, and start again. A
		-- setting that visibly does nothing is indistinguishable from a bug.
		if MM.Weights.blockedByRoute then
			return false, "the plan is still being gated on an active route"
		end
		return true, "no route gate on applying weights"
	end)

	check("Planning a mount moves it, rather than copying it", function()
		-- The left pane listed everything missing INCLUDING what was already
		-- planned, so a player with a full plan saw a list where every row said
		-- IN PLAN and offered to remove it -- the pane meant to answer "what
		-- could I add" was answering "what have you already added".
		local P = MM.Planner
		if not (P and P.GetMissing and P.InPlan) then return nil, "no planner" end
		local all = P:GetMissing()
		if #all == 0 then return nil, "nothing missing to test" end
		local planned, unplanned = 0, 0
		for _, e in ipairs(all) do
			if P:InPlan(e.spellID) then planned = planned + 1
			else unplanned = unplanned + 1 end
		end
		-- GetMissing is the raw truth and SHOULD still contain both; the split
		-- is the view's job. This asserts the two sets are complementary, which
		-- is what makes moving between panes lossless.
		if planned + unplanned ~= #all then
			return false, "a missing mount was neither planned nor unplanned"
		end
		return true, ("%d missing: %d on the plan, %d still to choose from")
			:format(#all, planned, unplanned)
	end)

	check("The missing list can be searched", function()
		-- The filter existed and was honoured by GetMissing long before there
		-- was any way to type into it. Proves the plumbing, not the widget:
		-- narrow to a name we know is present and confirm the list shrinks.
		local P = MM.Planner
		if not (P and P.GetMissing and P.filters) then return nil, "planner unavailable" end
		local saved = P.filters.search
		P.filters.search = ""
		local all = #P:GetMissing()
		if all == 0 then P.filters.search = saved; return nil, "nothing missing to search" end
		local pick = P:GetMissing()[1].name
		P.filters.search = pick
		local narrowed = #P:GetMissing()
		P.filters.search = "zzzzq-no-such-mount"
		local none = #P:GetMissing()
		P.filters.search = saved
		if narrowed >= all or narrowed == 0 then
			return false, ("search for %q returned %d of %d"):format(pick, narrowed, all)
		end
		if none ~= 0 then return false, "a nonsense search still matched " .. none end
		return true, ("%d missing; %q narrows to %d, nonsense to 0")
			:format(all, pick, narrowed)
	end)

	check("A deliberate no-location stop is not a blocker", function()
		-- This shipped as a false blocker in both the scorecard AND the release
		-- gate, independently. Router.lua puts the no-location goals into the
		-- route on purpose, flagged, so skipping through cannot "finish" while
		-- they sit unvisited — and both readers called that "not shippable".
		--
		-- A gate that cries wolf gets ignored, which costs more than having no
		-- gate at all. So the excuse is checked here against the LIVE route.
		local R = MM.Router
		if not (R and R.route and #R.route > 0) then return nil, "no route yet" end
		local flagged, unexplained, goals = 0, 0, 0
		for _, stop in ipairs(R.route) do
			goals = goals + #(stop.members or { stop })
			if not stop.world then
				if stop.noLocation or stop.unmappedZone
					or (MM.Queue and MM.Queue.KindFor and MM.Queue.KindFor(stop.rec))
				then flagged = flagged + 1
				else unexplained = unexplained + 1 end
			end
		end
		if unexplained > 0 then
			return false, ("%d stops have no position and no reason"):format(unexplained)
		end
		-- And the reconciliation must count GOALS, not stops: a stop can carry
		-- eight mounts, and counting stops "loses" the other seven.
		local plan = #MM.Planner:GetPlan()
		local total = goals + #(R.deferred or {})
		if total ~= plan then
			return false, ("%d goals across %d stops + %d deferred = %d, plan holds %d")
				:format(goals, #R.route, #(R.deferred or {}), total, plan)
		end
		return true, ("%d goals across %d stops reconcile exactly; "
			.. "%d positionless stops all carry a reason")
			:format(goals, #R.route, flagged)
	end)

	check("The scorecard can go down", function()
		-- A scorecard that only ever reads 100 is a decoration. Every
		-- dimension must be able to pull the number down, or the addon is
		-- grading its own homework with a pen it cannot lift.
		--
		-- Checked here against LIVE state rather than a fixture: the offline
		-- harness proves each dimension responds, this proves the thing is
		-- wired to reality and produces a number at all.
		local Sc = MM.Score
		if not (Sc and Sc.Compute) then return false, "scorecard missing" end
		local score, rows, possible, unmeasured = Sc.Compute()
		if not score then return nil, "nothing measurable yet — run /mm report" end
		if score < 0 or score > 100 then
			return false, ("scored %.1f, which is not a percentage"):format(score)
		end
		-- Every measured row must name its blocker when it is not full marks.
		for _, r in ipairs(rows) do
			if r.frac and r.frac < 0.999 and not r.why then
				return false, ("%s lost points without saying why"):format(r.d.label)
			end
		end
		local items, quests = Sc.Unreachable()
		return true, ("%.1f/100 across %d measurable points (%d unmeasurable); "
			.. "%d item + %d quest conditions excluded as platform limits")
			:format(score, possible, unmeasured, items, quests)
	end)

	check("The report does not freeze the client", function()
		-- Requirement — then generate report froze the wow client. Five sections
		-- were added in one session and none of them were measured. Between
		-- them the report ran the whole self-test TWICE (three route builds
		-- inside it), walked the database calling C_Item.GetItemCount per
		-- reagent twice over, and repeated an 8,000-pcall scan for a second
		-- section that wanted the same answer.
		--
		-- A budget, so the next such regression is a red line here rather than
		-- a locked-up client. Generous on purpose: this is a floor against
		-- catastrophe, not a micro-benchmark.
		local D = MM.Diagnostics
		if not (D and D.Build) then return nil, "report builder not exposed" end
		if not debugprofilestop then return nil, "no timer on this client" end
		-- MUST NOT RUN INSIDE THE REPORT.
		--
		-- The report's first section runs the whole self-test, which would run
		-- this check, which builds the report, which runs the self-test... I
		-- nearly shipped a fix for a freeze that caused an infinite one. The
		-- guard is the same flag the release gate uses, for the same reason.
		-- Both phases, not just the build. Timing the report means BUILDING one,
		-- and a build runs this suite -- so this must stand down whenever the
		-- suite is being run on a report's behalf, whichever half it is in.
		if D.inReport or (MM.Tests and MM.Tests.preparing) then
			return nil, "skipped while a report is being made — it would recurse"
		end
		local t0 = debugprofilestop()
		local ok, err = pcall(D.Build)
		local ms = debugprofilestop() - t0
		if not ok then return false, "the report errored: " .. tostring(err) end

		-- TOTAL TIME STOPPED BEING THE NUMBER THAT MATTERS.
		--
		-- `/mm report` died with "script ran too long" on a slower machine while
		-- this check was green here, because the client's watchdog measures ONE
		-- uninterrupted run and this measured the sum. The report breathes
		-- between sections now, so what can still trip it is a single section
		-- that is too big on its own -- and that is what to hold a line under.
		-- The total stays below as a floor against catastrophe.
		local worst, worstMs = D.SlowestSection and D.SlowestSection()
		if worst and worstMs and worstMs > 400 then
			return false, ("one section runs %d ms without yielding (%s) — the "
				.. "watchdog measures a single run, not the total")
				:format(worstMs, worst)
		end
		local BUDGET = 2000
		if ms > BUDGET then
			return false, ("the report took %.0f ms — over the %d ms budget. "
				.. "Something expensive is being done per record, or twice.")
				:format(ms, BUDGET)
		end
		return true, ("the whole report builds in %.0f ms (budget %d)"):format(ms, BUDGET)
	end)

	check("No diagnostic is unreachable from the report", function()
		-- Requirement — i do not care about slash commands as long as its all
		-- captured. The pasted report is the artefact that matters; a
		-- diagnostic reachable only by typing a command nobody knows exists is
		-- the same as not having written it.
		--
		-- This walks every registered _DEBUG handler in the addon and requires
		-- it to appear in the report's own section list. An orphan is a fact
		-- that will never reach the person reading the paste.
		local D = MM.Diagnostics
		if not (D and D.SECTIONS) then return nil, "sections are not exposed" end
		local listed = {}
		for _, sec in ipairs(D.SECTIONS) do
			if type(sec[2]) == "string" then listed[sec[2]] = true end
		end
		local orphans = {}
		for event in pairs(MM.Handlers or {}) do
			if type(event) == "string" and event:match("_DEBUG$") and not listed[event] then
				orphans[#orphans + 1] = event
			end
		end
		if #orphans > 0 then
			table.sort(orphans)
			return false, ("%d diagnostics exist but never reach the report: %s")
				:format(#orphans, table.concat(orphans, ", ", 1, math.min(#orphans, 4)))
		end
		local n = 0
		for _ in pairs(listed) do n = n + 1 end
		return true, ("%d diagnostics, every one of them in the report"):format(n)
	end)

	check("Every diagnostic section actually produces output", function()
		-- Requirement — all of those should be in the diagnostics right -- and he
		-- was right that some of it only existed in conversation. A section
		-- that silently produces nothing is worse than a missing one: the
		-- report LOOKS complete while the answer is absent, which is how a
		-- reader ends up asking a person instead.
		local D = MM.Diagnostics
		if not (D and D.SECTIONS) then return nil, "sections are not exposed" end
		local silent, ran = {}, 0
		local realPrint = MM.Print
		for _, sec in ipairs(D.SECTIONS) do
			local name, src = sec[1], sec[2]
			if type(src) == "string" then
				local lines = 0
				MM.Print = function() lines = lines + 1 end
				local ok = pcall(function() MM:Fire(src) end)
				MM.Print = realPrint
				ran = ran + 1
				if not ok then
					return false, ("section %q errored"):format(name)
				end
				if lines == 0 then silent[#silent + 1] = name end
			end
		end
		MM.Print = realPrint
		if #silent > 0 then
			return false, ("%d sections produce no output: %s"):format(#silent,
				table.concat(silent, ", ", 1, math.min(#silent, 5)))
		end
		return true, ("%d event-driven sections all report something"):format(ran)
	end)

	check("A weekly cap is arithmetic, not an estimate", function()
		-- Resolving currency ids unlocked something the addon could not see:
		-- GetCurrencyInfo reports a weekly cap. Needing 3,000 more of something
		-- capped at 750 a week is FOUR WEEKS, and playing harder cannot change
		-- it. That is the difference between "about eight hours" and "not before
		-- the 24th".
		local C = MM.Conditions
		if not (C and C.CurrencyWeeks) then return false, "CurrencyWeeks missing" end
		-- A cap that is a boolean rather than a number must be refused: some
		-- builds return true/false for canEarnPerWeek, and treating that as a
		-- quantity produces a confident nonsense estimate.
		local bogus = { type = "CURRENCY", id = -1, amount = 100 }
		if C.CurrencyWeeks(bogus) ~= nil then
			return false, "an unreadable currency produced a week count"
		end
		local capped, uncapped, done = 0, 0, 0
		for _, entry in ipairs(MM.Planner:GetPlan()) do
			for _, cond in ipairs((entry.rec and entry.rec.conditions) or {}) do
				if cond.type == "CURRENCY" and cond.id then
					local weeks = C.CurrencyWeeks(cond)
					if weeks == nil then uncapped = uncapped + 1
					elseif weeks == 0 then done = done + 1
					else
						capped = capped + 1
						if weeks < 0 or weeks > 520 then
							return false, ("%s reports %d weeks, which is not a real answer")
								:format(cond.name or "a currency", weeks)
						end
					end
				end
			end
		end
		if capped + uncapped + done == 0 then
			return nil, "no currency conditions carry an id yet — run /mm resolve"
		end
		return true, ("%d capped currencies costed as weeks, %d uncapped fall back "
			.. "to the grind estimate, %d already affordable")
			:format(capped, uncapped, done)
	end)

	check("An id we supplied lands ON the condition, not beside it", function()
		-- SetConditionID exists because OverrideMount cannot do this job.
		-- conditionKey treats an id as authoritative, so merging in
		-- { type = "QUEST", name = "...", id = N } keys as QUEST\0N while the
		-- record's own condition keys as QUEST\0<name> -- and the record ends
		-- up requiring the same quest TWICE, once with progress and once
		-- without. The planner would then charge it twice.
		--
		-- So: no record may carry two conditions of one type naming the same
		-- thing. This fails the moment someone reaches for OverrideMount again.
		local dupes, checked, withID = 0, 0, 0
		local firstBad
		for _, rec in ipairs(MM.DBList or {}) do
			local seen = {}
			for _, c in ipairs(rec.conditions or {}) do
				if c.name then
					local key = (c.type or "?") .. "\0" .. c.name:lower()
					if seen[key] then
						dupes = dupes + 1
						firstBad = firstBad or (rec.name .. " / " .. c.name)
					end
					seen[key] = true
					checked = checked + 1
					if c.id then withID = withID + 1 end
				end
			end
		end
		if dupes > 0 then
			return false, ("%d duplicated condition(s), e.g. %s -- an id was "
				.. "merged in as a NEW condition instead of annotating the old one")
				:format(dupes, tostring(firstBad))
		end
		return true, ("%d named conditions, %d carry an id, 0 duplicated")
			:format(checked, withID)
	end)

	check("No goal points at the exact middle of its zone", function()
		-- 50/50 is what a coordinate looks like when nobody knew, and it is
		-- worse than an absent one: "zone centre only" is handled honestly
		-- everywhere, while a placeholder passes every check that asks whether
		-- an x exists rather than whether it means anything.
		--
		-- Found because a player reported the Nether-Swept Drake's real spot as
		-- 50/30 and ours read 50/50. Forty-seven others were the same.
		local bad, firstBad, positioned = 0, nil, 0
		for _, rec in ipairs(MM.DBList or {}) do
			local z = rec.zone
			if z and z.x and z.y then
				positioned = positioned + 1
				if z.x == 50 and z.y == 50 then
					bad = bad + 1
					firstBad = firstBad or rec.name
				end
			end
		end
		if bad > 0 then
			return false, ("%d goal(s) sit at exactly 50/50, e.g. %s -- that is "
				.. "the zone centre, not a location"):format(bad, tostring(firstBad))
		end
		return true, ("%d positioned goals, none at the zone centre"):format(positioned)
	end)

	check("A row's action button is in front of its row", function()
		-- [+] worked in Collection, [-] worked in the Planner's right pane, and
		-- [+] in the Planner's LEFT pane did nothing. All three are the same
		-- helper with the same anchor and the same handler -- audited line by
		-- line -- so the handler was never the problem and the click was not
		-- arriving. A child Button at its parent's frame level does not
		-- reliably win the hit test; the two panes that worked were winning it
		-- by creation order.
		--
		-- Builds a real row and a real action button and asserts the ordering,
		-- because that is the property that was implicit and therefore free to
		-- change without anyone noticing.
		-- MM.UI, not UI. This file has no `UI` local, so the bare name was a
		-- global nil and the check threw on its own first line -- a test that
		-- fails for a reason unrelated to what it tests is worse than no test,
		-- because it spends a release gate on itself.
		local UI = MM.UI
		if not (UI and UI.MakeRowAction) then
			return nil, "row action helper unavailable"
		end
		local row = CreateFrame("Button", nil, UIParent)
		row:SetSize(400, 46)
		local b = UI.MakeRowAction(row)
		local rl, bl = row:GetFrameLevel(), b:GetFrameLevel()
		local clicks = b.GetAttribute and true
		row:Hide()
		if bl <= rl then
			return false, ("the action button sits at level %d on a row at %d -- "
				.. "the row can take the click"):format(bl, rl)
		end
		return true, ("action button at level %d over a row at %d%s")
			:format(bl, rl, clicks and ", clicks registered explicitly" or "")
	end)

	check("Adding to the plan from any pane actually adds", function()
		-- The failure looked like a dead button; it would look identical if
		-- Add silently declined. Drives the same call the [+] handlers make,
		-- with a spellID that is definitely not planned, and puts the plan back
		-- exactly as it was.
		--
		-- IT ALSO SAYS WHAT THE ROUND TRIP COST, AND TO WHOM.
		--
		-- This check is the addon's one release blocker and has been for several
		-- builds, at ~3.6 seconds against a watchdog that kills long checks. The
		-- work is not here -- Add and Remove are a table insert and a table
		-- remove -- it is in the two MM_PLAN_CHANGED fires they raise, and ten
		-- handlers listen. "Over budget" names none of them, so the number was
		-- unactionable and stayed that way.
		--
		-- MM.Profile already accumulates milliseconds per (message, handler);
		-- nothing read it for this. Snapshotting it either side of the round
		-- trip costs one table walk and turns the blocker into a named
		-- handler, which is the difference between a fix and a theory.
		local P = MM.Planner
		if not (P and P.Add and P.Remove and P.InPlan) then return nil, "no planner" end
		local probe = -424242            -- cannot collide with a real spell id
		if P:InPlan(probe) then return nil, "probe id already planned" end

		local function snapshot()
			local out, bucket = {}, MM.Profile and MM.Profile["MM_PLAN_CHANGED"]
			if bucket then
				for name, row in pairs(bucket) do out[name] = row.ms end
			end
			return out
		end

		local before = #MM.cdb.plan
		local costBefore = snapshot()
		local startedAt = debugprofilestop and debugprofilestop() or nil
		P:Add(probe)
		local added = P:InPlan(probe) ~= nil
		P:Remove(probe)
		local elapsed = startedAt and (debugprofilestop() - startedAt) or nil
		local restored = (#MM.cdb.plan == before) and not P:InPlan(probe)
		if not added then
			return false, "Planner:Add did not put the goal on the plan"
		end
		if not restored then
			return false, "Planner:Remove did not undo the probe -- plan left dirty"
		end

		-- Who spent it, worst first. Anything under a millisecond is noise and
		-- would only bury the one line worth reading.
		local costAfter, rows = snapshot(), {}
		for name, ms in pairs(costAfter) do
			local spent = ms - (costBefore[name] or 0)
			if spent >= 1 then rows[#rows + 1] = { name = name, ms = spent } end
		end
		table.sort(rows, function(a, b) return a.ms > b.ms end)
		local blame = {}
		for i = 1, math.min(#rows, 3) do
			blame[#blame + 1] = ("%s %.0f ms"):format(rows[i].name, rows[i].ms)
		end
		-- Router's share is three steps, and it records the split itself. A
		-- handler total counts the nested MM_ROUTE_ADVANCED fire as well, so
		-- without this the number reads as a slow rebuild when the rebuild is
		-- independently measured at ~50 ms.
		local split = MM.Router and MM.Router.lastPlanEditMs
		if split then
			blame[#blame + 1] = ("Router splits as build %.0f / waypoint %.0f / route-advanced %.0f ms")
				:format(split.build or 0, split.waypoint or 0, split.advanced or 0)
		end

		return true, ("add and remove round-trip on a %d-goal plan%s%s"):format(
			before,
			elapsed and (" in %.0f ms"):format(elapsed) or "",
			#blame > 0 and (" -- spent in: " .. table.concat(blame, ", ")) or "")
	end)

	check("Every kind of client string we depend on is still readable", function()
		-- Five reports, five shapes, one 12.0 change -- and every time, the
		-- honest answer to "what else is secret?" was a guess.
		--
		-- Guarding everything would cost capability. Map names, achievement
		-- names and item names are read constantly, and routing and 5,438
		-- achievement ids depend on them; wrapping those in a degrade-to-nil
		-- path trades a fault the client does not have for a feature that stops
		-- working. So this measures instead, against LIVE values.
		--
		-- A class marked `relied` going secret is a broken addon rather than a
		-- degraded one, and fails here. A class we merely use opportunistically
		-- -- a vignette name, a POI name -- degrades by design and is reported
		-- rather than failed.
		local U = MM.Util
		if not (U and U.SecretAudit) then return nil, "the audit is not loaded" end
		local rows = U.SecretAudit()
		local broken, degraded, known, unavailable = {}, {}, 0, 0
		for _, r in ipairs(rows) do
			if r.state == "WITHHELD" then
				if r.relied then broken[#broken + 1] = r.label
				else degraded[#degraded + 1] = r.label end
			elseif r.state == "readable" then known = known + 1
			else unavailable = unavailable + 1 end
		end
		if #broken > 0 then
			return false, ("this client withholds %d string(s) the addon depends on: %s")
				:format(#broken, table.concat(broken, ", "))
		end
		if known == 0 then return nil, "nothing could be probed here" end
		return true, ("%d of %d kinds readable, %d not probeable here%s")
			:format(known, #rows, unavailable,
				#degraded > 0 and (", withheld but non-essential: "
					.. table.concat(degraded, ", ")) or "")
	end)

	check("A client string we cannot read degrades, it does not throw", function()
		-- 12.0 secret values. Reported twice: once as every boss kill throwing,
		-- then AGAIN from delve combat, because the first fix lived in Scanner
		-- rather than anywhere a client-supplied string is read. Patching each
		-- site as it is reported is how the second one happened.
		--
		-- THE FIRST FIXTURE HERE MODELLED THE WRONG FAILURE, and passed for it.
		-- It used a value whose CONCATENATION errors, on the belief that this is
		-- what a secret does. It is not: a secret concatenates and yields
		-- another secret, and the COMPARISON afterwards throws. The helper was
		-- testing its result outside the pcall, so the guard itself became the
		-- error site -- and this check watched that happen and reported PASS,
		-- because it was asking about a shape the client does not produce.
		--
		-- Plain Lua cannot reproduce the real one exactly: `s ~= ""` compares a
		-- table against a string, and Lua answers that without ever consulting
		-- __eq. So the second fixture models what it can -- a concatenation that
		-- SUCCEEDS and yields something unusable -- and the helper is written so
		-- every operation happens inside the pcall, which is the property that
		-- actually matters and cannot be faked by a fixture.
		local U = MM.Util
		if not (U and U.ReadableString) then return nil, "helper unavailable" end
		local secret = setmetatable({}, {
			__concat = function() error("attempt to perform string conversion "
				.. "on a secret value", 0) end,
			__tostring = function() error("secret", 0) end,
		})
		local before = U.secretReads or 0
		local got = U.ReadableString(secret)
		if got ~= nil then
			return false, ("a value that cannot be concatenated returned %q")
				:format(tostring(got))
		end
		if (U.secretReads or 0) <= before then
			return false, "the refusal was not counted, so nothing can report it"
		end
		-- Second shape: the concatenation works and what comes back is not a
		-- string. The old helper happened to survive this one, but by luck --
		-- and luck is what moving the tests inside the pcall removes.
		local surviving = setmetatable({}, {
			__concat = function() return setmetatable({}, {}) end,
		})
		if U.ReadableString(surviving) ~= nil then
			return false, "a concatenation that yields a non-string was accepted"
		end
		if U.ReadableString("Alunira") ~= "Alunira" then
			return false, "a readable string did not survive the helper"
		end
		if U.ReadableString(nil) ~= nil then
			return false, "nil should stay nil rather than becoming a string"
		end
		return true, ("secret refused and counted, plain strings pass through "
			.. "(%d refusals this session)"):format(U.secretReads or 0)
	end)

	check("Only one copy of this addon is loaded", function()
		-- Reported as three separate bugs at once -- the celebration printing
		-- twice, two plan windows in two different styles, and a plan that
		-- appeared to rewrite itself. All one cause: an old folder left beside
		-- the current one, loading as a second addon.
		--
		-- An addon cannot see another addon's frames, but it can read the
		-- addon list, so this is knowable rather than guessable.
		if not MM.ConflictingCopies then return nil, "detector unavailable" end
		local dupes = MM.ConflictingCopies()
		if #dupes > 0 then
			return false, ("another Master Mounts is loaded (%s) -- delete the "
				.. "old AddOns folder"):format(table.concat(dupes, ", "))
		end
		return true, "one copy loaded"
	end)

	check("A live-location lookup never hides a goal when it fails", function()
		-- THE SAME MISTAKE, TWICE, IN ONE SESSION. Treasures were built so that
		-- a missing POI only ever costs a waypoint. The rotating gate one file
		-- away returned ROTATION instead, which HIDES the goal -- and the live
		-- report caught it: the held-back count went 14 to 15 and the Grand
		-- Hunt, which had been the top recommendation, left the plan.
		--
		-- The same report also shows it would have stayed gone:
		-- GetAreaPOIForMap on a zone you are not standing in returns permanent
		-- landmarks and no event POIs at all.
		--
		-- So the rule, asserted rather than remembered: a lookup that exists to
		-- IMPROVE a location may never decide availability.
		local checked, bad = 0, nil
		for _, rec in ipairs(MM.DBList or {}) do
			if rec.obtainable and (rec.rotating or rec.poi) then
				checked = checked + 1
				local status = MM.Availability.GetStatus({ rec = rec, spellID = rec.spellID })
				-- LOCKED is legitimate: a rotating event really can be done for
				-- the week. ROTATION from a failed POI read is not.
				if status == "ROTATION" then
					bad = bad or rec.name
				end
			end
		end
		if checked == 0 then return nil, "no live-location records in the database" end
		if bad then
			return false, ("%s is held back as ROTATION by a location lookup -- "
				.. "failing to find a POI must cost a waypoint, not a goal")
				:format(bad)
		end
		return true, ("%d records take their location from the map; none of them "
			.. "is gated by it"):format(checked)
	end)

	check("A rotating event is found where it is, not where it usually is", function()
		-- Reported from play: the Grand Hunt led the plan, sent the player to a
		-- fixed Ohn'ahran Plains coordinate, and then never registered as done.
		-- A hunt rotates between the four Dragonflight zones, so a stored zone
		-- is right about a quarter of the time.
		local A = MM.Assaults
		if not (A and A.FindRotating and A.WeeklyDone) then
			return nil, "rotating gates unavailable"
		end
		local gates, records = 0, 0
		for _, rec in ipairs(MM.DBList or {}) do
			if rec.rotating then
				records = records + 1
				if not rec.rotating.key then
					return false, ("%s has a rotating gate with no key -- weekly "
						.. "completion cannot be stored against it"):format(rec.name)
				end
				if not (rec.rotating.maps and #rec.rotating.maps > 1) then
					return false, ("%s names %d map(s); a gate that rotates needs "
						.. "more than one"):format(rec.name,
						rec.rotating.maps and #rec.rotating.maps or 0)
				end
				-- The stored zone must still exist as a FALLBACK: when the
				-- client cannot be asked, a zone beats nothing.
				if not (rec.zone and rec.zone.mapID) then
					return false, ("%s has no fallback zone for when the live POI "
						.. "cannot be read"):format(rec.name)
				end
			end
		end
		for _ in pairs(A.rotatingGates or {}) do gates = gates + 1 end
		if records == 0 then return nil, "no rotating records in the database" end
		-- Registration is DISCOVERED from the database at login, so a record
		-- that declares a gate and never gets watched is the failure to catch.
		if gates == 0 then
			return nil, "gates not collected yet -- they are gathered at login"
		end
		return true, ("%d rotating record(s), %d gate(s) watched for a weekly "
			.. "turn-in"):format(records, gates)
	end)

	check("A paragon cache is a completion the router acts on", function()
		-- Asked directly whether paragon mounts register and move the route on.
		-- They did not: an attempt came from a combat-log kill (off in 12.0),
		-- an encounter name, or a trackingQuest that NO record carries.
		local A = MM.Attempts
		if not (A and A.IsParagonGoal) then return nil, "attempts module unavailable" end
		local goals, withFaction = 0, 0
		for _, rec in ipairs(MM.DBList or {}) do
			if rec.obtainable and A.IsParagonGoal(rec) then
				goals = goals + 1
				for _, c in ipairs(rec.conditions or {}) do
					if c.type == "REP" and c.standingName == "Paragon" and c.factionID then
						withFaction = withFaction + 1
						break
					end
				end
			end
		end
		if goals == 0 then return nil, "no paragon goals in the database" end
		-- A paragon goal with no factionID cannot be watched: hasRewardPending
		-- is asked per faction, so the id is the whole mechanism.
		if withFaction < goals then
			return false, ("%d of %d paragon goals carry no factionID, so their "
				.. "cache cannot be watched"):format(goals - withFaction, goals)
		end
		return true, ("%d paragon goals, every one with a factionID to watch")
			:format(goals)
	end)

	check("A treasure POI improves a location and never gates one", function()
		-- The whole safety property of reading treasure POIs: absence is
		-- ambiguous -- looted, undiscovered, filtered off, zone not loaded --
		-- so a miss must fall back to what the record already said and must
		-- never hide, block or complete a goal.
		local A = MM.Assaults
		if not (A and A.FindPOI) then return nil, "poi lookup unavailable" end
		local gated, noZone = 0, 0
		for _, rec in ipairs(MM.DBList or {}) do
			if rec.poi then
				gated = gated + 1
				-- Without a zone mapID there is no map to scan, so the gate
				-- would silently never fire.
				if not (rec.zone and rec.zone.mapID) then noZone = noZone + 1 end
				-- A location must survive the POI being unreadable.
				local loc = MM.GetRecordLocation(rec)
				if not loc then
					return false, ("%s has a poi gate and no location at all when "
						.. "the POI cannot be read"):format(rec.name)
				end
				-- And it must not have become a status gate by accident.
				local status = MM.Availability.GetStatus({ rec = rec, spellID = rec.spellID })
				if status == "ROTATION" or status == "LOCKED" then
					return false, ("%s is gated %s by a treasure POI -- absence "
						.. "must never block a goal"):format(rec.name, status)
				end
			end
		end
		if gated == 0 then return nil, "no treasure POI gates in the database" end
		if noZone > 0 then
			return false, ("%d poi gate(s) have no zone mapID, so nothing is ever "
				.. "scanned for them"):format(noZone)
		end
		return true, ("%d treasures read their point from the map, and fall back "
			.. "to their zone when it cannot be read"):format(gated)
	end)

	check("A profession you lack only blocks what nobody can make for you", function()
		-- All 22 profession records were held back identically, and they are
		-- three situations: five need the profession TO RIDE, two are
		-- Archaeology solves nobody can do for you, and fifteen are BoE or
		-- ordinary crafting orders. Holding the fifteen back hid obtainable
		-- mounts from the plan entirely.
		--
		-- The distinction lives in each source line and is set per record, so
		-- the failure to catch is a record drifting into the wrong group -- or
		-- the whole flag being ignored again.
		local prof, tradeable, priced = 0, 0, 0
		for _, rec in ipairs(MM.DBList or {}) do
			if rec.obtainable then
				local hasProf = false
				for _, c in ipairs(rec.conditions or {}) do
					if c.type == "PROFESSION" then hasProf = true break end
				end
				if hasProf then
					prof = prof + 1
					if rec.tradeable then
						tradeable = tradeable + 1
						-- A tradeable craft must still cost SOMETHING, or making
						-- it available turns it into a free mount that outranks
						-- real work. Reagents are the honest floor.
						local hasCost = false
						for _, c in ipairs(rec.conditions or {}) do
							if c.type == "MATERIAL" or c.type == "ITEM" then hasCost = true end
						end
						if hasCost then priced = priced + 1 end
					end
				end
			end
		end
		if prof == 0 then return nil, "no profession records in the database" end
		if tradeable == 0 then
			return false, ("all %d profession records are blocked -- the tradeable "
				.. "flag is not reaching them"):format(prof)
		end
		if priced < tradeable then
			return false, ("%d tradeable craft(s) carry no reagent or item cost, so "
				.. "they would rank as free work"):format(tradeable - priced)
		end
		return true, ("%d profession records: %d somebody else can make, all with a "
			.. "real cost; %d genuinely blocked"):format(prof, tradeable, prof - tradeable)
	end)

	check("An item cost asks how many, not whether any", function()
		-- Reported from outside: told to go and buy the Asset Advocator "even
		-- though I didn't have the currency for it". evalItem asked
		-- GetItemCount(id) > 0 and ignored the amount, so 1 of 25 Miscellaneous
		-- Mechanica satisfied the requirement and the mount ranked as a pickup.
		--
		-- Sixty conditions in the database want more than one. Driven through
		-- C.Evaluate rather than by reading the record, because the bug was in
		-- the evaluator while the data was right the whole time.
		local C = MM.Conditions
		if not (C and C.Evaluate) then return nil, "conditions module unavailable" end
		-- An item nobody owns: any real id works, since the assertion is about
		-- the COMPARISON, not about this character's bags.
		local probe = { type = "ITEM", id = 6948, name = "probe", amount = 999999 }
		local met = C.Evaluate(probe)
		if met ~= false then
			return false, ("a 999,999 requirement evaluated as %s -- the amount "
				.. "is being ignored"):format(tostring(met))
		end
		local one = { type = "ITEM", id = 6948, name = "probe", amount = 1 }
		local metOne = C.Evaluate(one)
		if metOne == nil then return nil, "item counts unreadable on this client" end
		local counted = 0
		for _, rec in ipairs(MM.DBList or {}) do
			for _, cond in ipairs(rec.conditions or {}) do
				if cond.type == "ITEM" and (cond.amount or 0) > 1 then
					counted = counted + 1
				end
			end
		end
		return true, ("%d item costs want more than one, and the amount decides")
			:format(counted)
	end)

	check("A subset is never reported as larger than its set", function()
		-- The soloability line prints "N carry no flag; M of those name an
		-- achievement id". M is a subset of N by construction, so M > N is not
		-- a wrong number, it is a sentence that cannot be true -- and it has
		-- now shipped twice, both times because the two loops drifted on one
		-- clause of the predicate.
		--
		-- Asserting the RELATIONSHIP rather than sharing the code, for the same
		-- reason the two unpriced lists are checked against each other: a
		-- helper both sides call can be extracted wrongly, and then the test
		-- agrees with the bug.
		local unknown, classified = 0, 0
		for _, rec in pairs(MM.DBByName or {}) do
			if rec.category == "ACHIEVEMENT" and rec.obtainable and rec.solo == nil then
				unknown = unknown + 1
				local id = MM.Conditions.RecordAchievementID
					and MM.Conditions.RecordAchievementID(rec)
				if id and MM.Conditions.AchievementClass(id) then
					classified = classified + 1
				end
			end
		end
		if unknown == 0 then return nil, "every achievement record carries a solo flag" end
		if classified > unknown then
			return false, ("%d of %d -- the subset is larger than the set it is "
				.. "drawn from, so the two counts use different predicates")
				:format(classified, unknown)
		end
		return true, ("%d unflagged, %d of them classified by the client")
			:format(unknown, classified)
	end)

	check("One cost is charged once, whichever field holds its id", function()
		-- The type+name check above cannot see this. ITEM and CURRENCY rows put
		-- their id in `id`, MATERIAL rows put it in `itemID`, so the same three
		-- booster parts appeared as three costs and three reagents and were
		-- charged twice over -- with different names, so no name test would
		-- have noticed either.
		--
		-- CollapseDuplicateCosts folds these at the end of the data build. This
		-- asserts the build actually ran it, which is the part that rots: the
		-- collapse used to be called from a file in the MIDDLE of the stack and
		-- nineteen layers landed after it.
		local COST = { ITEM = true, CURRENCY = true, MATERIAL = true }
		local dupes, firstBad, costs = 0, nil, 0
		for _, rec in ipairs(MM.DBList or {}) do
			local seen = {}
			for _, c in ipairs(rec.conditions or {}) do
				if COST[c.type] then
					costs = costs + 1
					local id = c.id or c.itemID or c.currencyID
					if id then
						if seen[id] then
							dupes = dupes + 1
							firstBad = firstBad or (rec.name .. " / " .. tostring(id))
						end
						seen[id] = true
					end
				end
			end
		end
		if dupes > 0 then
			return false, ("%d cost(s) counted twice, e.g. %s -- id and itemID key "
				.. "apart, so the same purchase survives as two conditions")
				:format(dupes, tostring(firstBad))
		end
		return true, ("%d cost conditions, none charged twice"):format(costs)
	end)

	check("A vendor that checks a reputation carries the condition", function()
		-- Reported from outside: "it told me to go collect Wild Goretusk but I
		-- don't have the rep needed." Nothing was broken in the evaluation --
		-- the record simply had no reputation condition, so allMet came back
		-- true and it ranked as a pickup.
		--
		-- A purchase whose source text names a standing and whose conditions
		-- name none is the exact shape of that bug, and it is cheap to spot:
		-- the words only appear in a source line because a vendor is checking
		-- for them. Six records were in this state, all of them routable.
		local STANDINGS = { "Exalted", "Revered", "Honored", "Friendly" }
		local PURCHASE = { VENDOR = true, CURRENCY = true, REP = true,
			TIMEWALKING = true }
		local bad, checked, firstBad = 0, 0, nil
		for _, rec in ipairs(MM.DBList or {}) do
			local src = rec.source or ""
			if rec.obtainable and PURCHASE[rec.category] then
				local names
				for _, s in ipairs(STANDINGS) do
					-- "with <Faction>" is what a gate reads like. "Exalted
					-- reputations" in a note about an achievement is not one,
					-- and matching the bare word flagged a dozen of those.
					if src:find(s .. " with ") then names = s break end
				end
				if names then
					checked = checked + 1
					local modelled = false
					for _, c in ipairs(rec.conditions or {}) do
						if c.type == "REP" then modelled = true break end
					end
					if not modelled then
						bad = bad + 1
						firstBad = firstBad or rec.name
					end
				end
			end
		end
		if checked == 0 then
			return nil, "no purchase record states a standing in its source text"
		end
		if bad > 0 then
			return false, ("%d purchase(s) name a reputation in prose and carry no "
				.. "REP condition, e.g. %s -- the planner will rank it as a pickup")
				:format(bad, tostring(firstBad))
		end
		return true, ("%d purchases state a standing; every one models it")
			:format(checked)
	end)

	check("A lockout retires its goal and moves nothing else", function()
		-- The plan is a chart, and it holds still while you follow it. Taking a
		-- lockout drops that ONE goal; the others keep the places they already
		-- had. Re-optimizing here would reshuffle objectives two and three
		-- while you were still flying to objective one.
		--
		-- So: nothing LOCKED may remain in the plan, and the removal must not
		-- have gone through the auto-optimize path.
		local P = MM.Planner
		if not (P and MM.cdb and MM.cdb.plan and MM.Availability) then
			return nil, "no plan"
		end
		local stuck, n = nil, 0
		for _, item in ipairs(MM.cdb.plan) do
			local entry = MM.Scanner and MM.Scanner.bySpell
				and MM.Scanner.bySpell[item.spellID]
			if entry then
				n = n + 1
				if MM.Availability.GetStatus(entry) == "LOCKED" then
					stuck = stuck or (entry.name or item.spellID)
				end
			end
		end
		if stuck then
			return false, ("%s is on lockout and still in the plan -- the router "
				.. "will keep pointing at something you cannot act on")
				:format(tostring(stuck))
		end
		if n == 0 then return nil, "plan is empty" end
		return true, ("%d planned goals, none of them on lockout"):format(n)
	end)

	check("A goal retired by a lockout is owed a place back", function()
		-- Retired and planned are mutually exclusive states. If a spellID is in
		-- both, the restore will re-add something already there; if it is in
		-- neither and was never collected or manually dropped, the goal has
		-- been lost silently -- which is the failure that matters, because the
		-- player never asked for it to go.
		local retired = MM.db and MM.db.retired
		if not retired then return nil, "nothing has been retired yet" end
		local planned, n = {}, 0
		for _, item in ipairs(MM.cdb and MM.cdb.plan or {}) do
			planned[item.spellID] = true
		end
		local both, collected = nil, 0
		for spellID in pairs(retired) do
			n = n + 1
			if planned[spellID] then both = both or spellID end
			local e = MM.Scanner and MM.Scanner.bySpell and MM.Scanner.bySpell[spellID]
			if e and e.collected then collected = collected + 1 end
		end
		if both then
			return false, ("spell %s is retired AND in the plan -- restoring it "
				.. "will add a goal that never left"):format(tostring(both))
		end
		if n == 0 then return nil, "nothing is retired right now" end
		return true, ("%d goal(s) retired by lockout, none of them still planned%s")
			:format(n, collected > 0
				and (", %d collected meanwhile and owed nothing"):format(collected) or "")
	end)

	check("The plan does not re-chart while you walk", function()
		-- The plan charts from where you STOOD when you charted it, not from
		-- wherever you are now. Reading the live position made objectives two
		-- and three reshuffle behind you while you flew to objective one.
		--
		-- So the anchor must exist and must be a stored place, not a live read.
		local P = MM.Planner
		if not (P and P.Anchor) then return nil, "planner not loaded" end
		local a = P.Anchor()
		if not a then return nil, "no plan has been charted yet" end
		local here = C_Map and C_Map.GetBestMapForUnit and C_Map.GetBestMapForUnit("player")
		-- A STORED ANCHOR AND A LIVE READ LOOK IDENTICAL UNTIL YOU MOVE.
		--
		-- This returned true either way, so standing where you charted -- the
		-- normal case -- made it assert a property it had no evidence for. It
		-- degrades there now and only claims the anchor held once the player
		-- has actually walked away from it.
		if not here then return nil, "cannot read where you are standing" end
		if here == a.mapID then
			return nil, ("standing where the plan was charted (%s) — a stored "
				.. "anchor and a live read cannot be told apart from here")
				:format(tostring(a.name))
		end
		return true, ("charted from %s (map %s) — you are in map %s now and the "
			.. "anchor has not followed"):format(
			tostring(a.name), tostring(a.mapID), tostring(here))
	end)

	check("Travel in the ease score is measured, not a continent flag", function()
		-- "Add 10 Easiest" was instant, and that was the tell -- it did no
		-- routing. Its whole travel input was 0 same continent / 1 another /
		-- 0.5 unknown, so on your own continent every candidate scored travel
		-- 0 and a mount 30 seconds away ranked level with one 8 minutes away.
		--
		-- A flag takes at most three values. A measurement takes many. This
		-- counts the DISTINCT travel costs the planner actually charges, which
		-- is the difference between the two and cannot be faked by a rename.
		local P = MM.Planner
		if not (P and P.CostParts and MM.Scanner) then return nil, "planner not loaded" end
		local seen, n, sample = {}, 0, 0
		for _, entry in ipairs(MM.Scanner.mounts or {}) do
			if not entry.collected and entry.rec and entry.rec.zone then
				local _, parts = P.CostParts(entry)
				for _, part in ipairs(parts or {}) do
					if part[1] == "Travel" then
						local v = math.floor((part[2] or 0) * 100 + 0.5)
						if not seen[v] then seen[v] = true n = n + 1 end
						sample = sample + 1
					end
				end
			end
			if sample >= 400 then break end
		end
		if sample == 0 then return nil, "no located candidates to price" end
		if n <= 3 then
			return false, ("%d distinct travel costs across %d goals -- that is a "
				.. "flag, not a measurement; the ease score is not using the router")
				:format(n, sample)
		end
		return true, ("%d distinct travel costs across %d goals"):format(n, sample)
	end)

	check("Currency and faction ids resolve from the client", function()
		-- The achievement fix exposed a pattern was not generalised: 127
		-- conditions named a faction with no id and 107 named a currency with
		-- no id. There is no reverse name->id call, but the id space is small
		-- and dense, and the client answers for every id that exists.
		local store = MM.db.ids or {}
		local function coverage(kind, field)
			local n, ok = 0, 0
			for _, rec in pairs(MM.DBByName) do
				for _, cond in ipairs(rec.conditions or {}) do
					if cond.type == kind then
						n = n + 1
						if cond[field] then ok = ok + 1 end
					end
				end
			end
			return n, ok
		end
		local cN, cOK = coverage("CURRENCY", "id")
		local fN, fOK = coverage("REP", "factionID")
		if not store.indexBuilt then
			return nil, "the id index has not been built yet (a few seconds after login)"
		end
		-- Ambiguous names must never be silently assigned.
		for _, tbl in ipairs({ store.currencies or {}, store.factions or {} }) do
			for name, id in pairs(tbl) do
				if id ~= false and type(id) ~= "number" then
					return false, ("index holds a non-id for %q"):format(name)
				end
			end
		end
		return true, ("currencies %d/%d, factions %d/%d carry ids")
			:format(cOK, cN, fOK, fN)
	end)

	check("Achievement ids come from the client", function()
		-- Requirement — close all gaps, get everything to 100%. Most remaining gaps
		-- genuinely cannot be closed from outside the game -- a drop rate nobody
		-- has observed does not exist to be looked up. This one can, and it had
		-- been treated as a lookup while sitting in the client all along.
		--
		-- Wowhead publishes an authoritative id on about one page in seven; the
		-- rest carry it only inside user comments. GetCategoryList plus
		-- GetAchievementInfo enumerates EVERY achievement with its real name and
		-- id -- the same table the Achievements UI reads.
		local named, withID, ambiguous = 0, 0, 0
		local store = MM.db.ids or {}
		for _, rec in pairs(MM.DBByName) do
			for _, cond in ipairs(rec.conditions or {}) do
				if cond.type == "ACHIEVEMENT" and cond.name then
					named = named + 1
					if cond.id then withID = withID + 1 end
				end
			end
		end
		for _ in pairs(store.achievementAmbiguous or {}) do ambiguous = ambiguous + 1 end
		if named == 0 then return nil, "no achievement conditions in the database" end
		if (store.achievementsSeen or 0) == 0 then
			return nil, "achievement index not built yet — run /mm resolve"
		end
		local pct = withID / named * 100
		if pct < 50 then
			return false, ("only %d of %d achievement conditions carry an id (%.0f%%)")
				:format(withID, named, pct)
		end
		return true, ("%d of %d achievement conditions carry an id (%.0f%%); "
			.. "%d names index %d achievements, %d names are ambiguous and left alone")
			:format(withID, named, pct, store.achievementsSeen or 0,
				store.achievementsSeen or 0, ambiguous)
	end)

	check("Every achievement id names the achievement the condition names", function()
		-- The same question the spell check asks, on the other side of the
		-- database: an id can be perfectly valid and still be attached to a
		-- name nobody can look up. Eight conditions DESCRIBED their achievement
		-- rather than naming it -- "Coiled Isle treasures" for what the game
		-- calls "Treasures of the Coiled Isle", "Mythic+ rating 2000" for
		-- "Midnight Keystone Master: Season 2" -- so a player who read the
		-- requirement and searched their achievement UI for it found nothing.
		--
		-- Faction qualifiers are stripped before comparing: the game gives
		-- 63099 and 63103 the same title, and this database tells them apart
		-- the way it tells the Vicious mounts apart.
		if not GetAchievementInfo then return nil, "no achievement API" end
		local checked, unread, wrong, bad = 0, 0, 0, {}
		for _, rec in pairs(MM.DBByName) do
			for _, cond in ipairs(rec.conditions or {}) do
				if cond.type == "ACHIEVEMENT" and cond.id and cond.name then
					local ok, _, title = pcall(GetAchievementInfo, cond.id)
					if not ok or not title or title == "" then
						unread = unread + 1
					else
						checked = checked + 1
						local mine = cond.name:gsub("%s*%(Horde%)$", "")
							:gsub("%s*%(Alliance%)$", "")
						if mine:lower() ~= title:lower() then
							wrong = wrong + 1
							if #bad < 3 then
								bad[#bad + 1] = ("%d is %q, we call it %q")
									:format(cond.id, title, cond.name)
							end
						end
					end
				end
			end
		end
		if checked == 0 then return nil, "no achievement resolved on this client" end
		if wrong > 0 then
			return false, ("%d of %d achievement id(s) name something else: %s")
				:format(wrong, checked, table.concat(bad, "; "))
		end
		return true, ("%d achievement id(s) checked against the client, every one "
			.. "named as the game names it%s"):format(checked,
			unread > 0 and ("; %d not readable here"):format(unread) or "")
	end)

	check("Every instance mount has a door", function()
		-- A mount with an `instance` block but no `zone` cannot be routed at
		-- all: it lands in "no location to route to" and silently never appears
		-- in a plan. Spawn of Galakras sat there while Kor'kron Juggernaut --
		-- the same raid, the same door -- carried the entrance all along.
		--
		-- This fails only for the COPYABLE case, where the answer is already in
		-- the database. A mount whose whole instance has no door recorded is a
		-- lookup nobody has done, not a mistake, and failing on it every build
		-- would train people to ignore the check.
		local withDoor, noDoor = {}, {}
		for _, rec in pairs(MM.DBByName) do
			if rec.obtainable and rec.instance and rec.instance.name then
				if rec.zone then
					withDoor[rec.instance.name] = rec.name
				else
					noDoor[#noDoor + 1] = rec
				end
			end
		end
		local copyable = {}
		for _, rec in ipairs(noDoor) do
			local donor = withDoor[rec.instance.name]
			if donor then
				copyable[#copyable + 1] = ("%s (copy from %s)"):format(rec.name, donor)
			end
		end
		if #copyable > 0 then
			return false, ("%d instance mounts have no door while an instance-mate "
				.. "does: %s"):format(#copyable, table.concat(copyable, ", ", 1,
					math.min(#copyable, 3)))
		end
		if #noDoor > 0 then
			return true, ("no copyable gaps; %d await a door nobody has recorded")
				:format(#noDoor)
		end
		return true, "every instance mount has a door"
	end)

	check("Queued goals are not given an arrow", function()
		-- Requirement — recommending queueing for a dungeon should be represented and
		-- respected in the nav. Infinite Timereaver drops from ANY Timewalking
		-- dungeon -- there is no door to stand outside. An arrow pointing at a
		-- location for those is worse than no arrow: it sends the player across
		-- the world to a spot that cannot help them.
		local Q = MM.Queue
		if not (Q and Q.Describe) then return false, "queue module missing" end
		local found, wrong = 0, {}
		for _, rec in pairs(MM.DBByName) do
			if rec.obtainable then
				local d = Q.Describe(rec)
				if d then
					found = found + 1
					if not d.label or d.label == "" then
						wrong[#wrong + 1] = rec.name
					end
				end
			end
		end
		if #wrong > 0 then
			return false, "queueable goals with no instruction: "
				.. table.concat(wrong, ", ", 1, math.min(#wrong, 4))
		end
		if found == 0 then return nil, "no queueable goals detected" end
		return true, ("%d goals reached by queueing, each with an instruction")
			:format(found)
	end)

	check("Detection does not over-claim a queue", function()
		-- A wrong arrow is recoverable; a wrong "just queue for it" wastes an
		-- evening in the wrong queue. So anything ambiguous must keep its arrow.
		local Q = MM.Queue
		if not (Q and Q.KindFor) then return false, "queue module missing" end
		-- A plain world drop must never be read as queueable.
		local fake = { name = "Test", source = "Drops from a rare in Nagrand",
			notes = "A rare spawn." }
		if Q.KindFor(fake) then
			return false, "a world drop was misread as a queue"
		end
		-- An explicit field must win over the text.
		local explicit = { name = "Test2", queue = "raid",
			source = "Drops from a rare in Nagrand" }
		local kind = Q.KindFor(explicit)
		if kind ~= "raid" then
			return false, "an explicit queue field was ignored"
		end
		return true, "world drops keep their arrow; explicit fields win"
	end)

	check("Cost coverage is counted, not assumed", function()
		-- Requirement — make sure everything has a cost ... highlight anything that
		-- does not. The effort floor keeps an unmodelled goal from being free,
		-- but it is one number for a whole category, so a goal resting on it is
		-- one whose place in the order cannot really be justified.
		--
		-- This does not demand full coverage -- 33% of plannable goals rest on
		-- the floor today and that is data entry, not a code defect. It demands
		-- that the number is KNOWN, because an uncounted gap is one nobody ever
		-- closes.
		local CO = MM.Contribute
		if not (CO and CO.CostCoverage) then return false, "cost coverage missing" end
		local byCat = CO.CostCoverage()
		local total, covered, worst, worstCat = 0, 0, 0, nil
		for cat, d in pairs(byCat) do
			total = total + d.total
			covered = covered + d.covered
			if #d.bare > worst then worst, worstCat = #d.bare, cat end
		end
		if total == 0 then return false, "no plannable goals found to audit" end
		-- Store, TCG and promotional mounts have no in-game cost because they
		-- have no in-game acquisition; they must not be counted as gaps.
		for cat in pairs(byCat) do
			if not MM.PLANNABLE[cat] then
				return false, "audited a non-plannable category: " .. cat
			end
		end
		return true, ("%d plannable goals: %d modelled, %d on the effort rating "
			.. "(worst: %s with %d)"):format(total, covered, total - covered,
			worstCat or "-", worst)
	end)

	check("Every estimate knows where it came from", function()
		-- the question was how realistic the time costs are. The honest answer is a
		-- ratio, not a number: how much of the plan's total is READ from the
		-- client and how much is a stand-in. That is only answerable if each
		-- part carries its own provenance -- reconstructing it by matching
		-- label text breaks the first time anyone rewords one.
		local P = MM.Planner
		if not (P and P.TimeCommitment) then return false, "planner missing" end
		local measured, assumed, untagged, goals = 0, 0, 0, 0
		for _, entry in ipairs(P:GetPlan()) do
			local minutes, parts = P.TimeCommitment(entry)
			if minutes and parts then
				goals = goals + 1
				for _, part in ipairs(parts) do
					if part.kind == "assumed" then assumed = assumed + part.minutes
					elseif part.kind == "measured" then measured = measured + part.minutes
					else untagged = untagged + 1 end
				end
			end
		end
		if untagged > 0 then
			return false, ("%d time parts carry no provenance"):format(untagged)
		end
		if goals == 0 then return nil, "nothing planned" end
		local total = measured + assumed
		if total <= 0 then return nil, "no time modelled yet" end
		return true, ("%d goals: %.0f%% measured, %.0f%% assumed"):format(
			goals, measured / total * 100, assumed / total * 100)
	end)

	check("A zone with no travel node learns its portal from the map", function()
		local PL = MM.PortalLearn
		if not PL then return false, "the portal learner is not loaded" end
		-- The PARSER is what can rot: Blizzard rewords a point of interest and
		-- the reader silently stops matching. Exercised against the exact
		-- strings this client was observed to publish.
		local dest, method = PL.Parse("Portal to Voidstorm")
		if dest ~= "Voidstorm" or method ~= "portal" then
			return false, ("a portal POI no longer parses: %s"):format(tostring(dest))
		end
		if PL.Parse("Portal Room") then
			return false, "a portal naming no destination is being read as one"
		end
		local zep = select(2, PL.Parse("Zeppelin to Orgrimmar"))
		if zep ~= "zeppelin" then
			return false, "a zeppelin is being priced as a portal"
		end
		-- Both ends, paired, with no client call involved.
		local pois = {
			{ mapID = 2395, rawName = "Portal to Voidstorm",
				position = { x = 0.42, y = 0.30 } },
			{ mapID = 2405, rawName = "Portal to Silvermoon",
				position = { x = 0.369, y = 0.59 } },
		}
		local links = PL.Pair(pois, function(n)
			return n == "Voidstorm" and 2405 or nil
		end, function() return nil end)
		if #links ~= 1 then
			return false, ("two published ends did not pair: %d link(s)"):format(#links)
		end
		local learned = PL.Count and PL.Count() or 0
		local scanned = PL.scanned
		if not scanned then
			return "degraded", "the map has not been scanned yet this session"
		end
		if learned == 0 then
			return true, ("%d map(s) read, no unjoined zone published a portal pair")
				:format(PL.mapsAsked or 0)
		end
		local names = {}
		for _, l in ipairs(PL.learned or {}) do
			local info = C_Map and C_Map.GetMapInfo and C_Map.GetMapInfo(l.toMap)
			names[#names + 1] = (info and info.name) or ("map " .. tostring(l.toMap))
		end
		return true, ("%d portal(s) learned from %d map(s): %s")
			:format(learned, PL.mapsAsked or 0, table.concat(names, ", "))
	end)

	check("A teleport you own as a toy counts as owned", function()
		-- EVERY ENGINEERING TELEPORT IS A TOY. They live in the toybox, not in
		-- the bags, so a possession check that counts bag stacks reports a
		-- maxed engineer as owning none of them -- and the option is then
		-- refused with a reason about something else entirely.
		--
		-- That is exactly what happened: the rule existed correctly in one
		-- place and was written a second time without the toybox. This asks
		-- the client which options it holds as toys and requires the addon's
		-- own answer to agree.
		local TP = MM.Teleports
		if not (TP and TP.Owned and TP.Options) then return false, "no teleports" end
		if not PlayerHasToy then
			return nil, "this client has no toybox API to cross-check against"
		end
		local toys, disagreed = 0, {}
		for _, o in ipairs(TP.All and TP.All() or {}) do
			if o.item then
				local isToy = false
				local ok, held = pcall(PlayerHasToy, o.item)
				if ok and held then isToy = true end
				if isToy then
					toys = toys + 1
					if not TP.Owned(o.item) then
						disagreed[#disagreed + 1] = o.name
					end
				end
			end
		end
		if #disagreed > 0 then
			return false, ("the client says you hold %d of these as toys, and "
				.. "%d are reported as not owned: %s"):format(toys, #disagreed,
					table.concat(disagreed, ", "))
		end
		if toys == 0 then
			return nil, "no teleport option is held as a toy on this character"
		end
		return true, ("%d teleport(s) held as toys, all counted as owned")
			:format(toys)
	end)

	check("Every spellID names the mount that carries it", function()
		-- 353 spell ids came out of Mount.db2, matched on NAME. That is a good
		-- source and a fallible join: two mounts can share a name, a name can
		-- be renamed between patches, and a wrong id here would quietly point
		-- the journal, the celebration and the plan at another mount.
		--
		-- The client can settle every one of them. GetMountFromSpell answers
		-- what a spell actually summons, so each id is asked to name itself.
		if not (C_MountJournal and C_MountJournal.GetMountFromSpell
			and C_MountJournal.GetMountInfoByID) then
			return nil, "this client cannot resolve a spell to a mount"
		end
		local checked, wrong, unresolved = 0, {}, 0
		for _, rec in pairs(MM.DBByName or {}) do
			if rec.spellID and not rec.stub then
				local ok, mountID = pcall(C_MountJournal.GetMountFromSpell, rec.spellID)
				if not (ok and mountID) then
					unresolved = unresolved + 1
				else
					local name = C_MountJournal.GetMountInfoByID(mountID)
					name = MM.Util.ReadableString(name)
					if name then
						checked = checked + 1
						-- TWO NAMING CONVENTIONS THIS DATABASE USES ON PURPOSE,
						-- and a spell id is not wrong for meeting either.
						--
						-- A faction qualifier: three records read "... (Alliance)"
						-- or "(Horde)" to tell a pair apart that the journal
						-- shows under one name.
						--
						-- A faction-neutral umbrella: "Chauffeured Chopper" is
						-- one record for a mount the journal lists per faction
						-- as the Mechano-Hog and the Mekgineer's Chopper. Its
						-- own notes say so -- "the spellID is what identifies
						-- it, not the name" -- which is exactly right and
						-- exactly what a name comparison cannot see.
						local ours = rec.name and rec.name:lower() or ""
						local theirs = name:lower()
						local bare = ours:gsub("%s*%((alliance|horde)%)$", "")
						bare = ours:gsub("%s*%(alliance%)$", ""):gsub("%s*%(horde%)$", "")
						local umbrella = rec.notes and rec.notes:find(
							"the spellID is what identifies it", 1, true) ~= nil
						if ours ~= theirs and bare ~= theirs and not umbrella then
							wrong[#wrong + 1] = ("%s carries %d, which summons %s")
								:format(rec.name, rec.spellID, name)
						end
					end
				end
			end
		end
		if #wrong > 0 then
			table.sort(wrong)
			return false, ("%d of %d spell ids summon a different mount: %s")
				:format(#wrong, checked, table.concat(wrong, "; ", 1,
					math.min(4, #wrong)))
		end
		if checked == 0 then
			return nil, "no spell id could be resolved on this client"
		end
		return true, ("%d spell ids checked against the client, every one names "
			.. "its own mount; %d not resolvable here"):format(checked, unresolved)
	end)

	check("Travel data is loaded and routable", function()
		-- Two datasets now decide most of the route's cost, and both fail the
		-- same silent way: a file left out of the .toc loads nothing, errors
		-- never, and the router quietly falls back to straight-line estimates
		-- that look plausible. Counting them is the only way that shows up.
		--
		-- Thresholds are deliberately well below the real figures. This is a
		-- "did it load at all" check, not a fingerprint of one build.
		local fs, tn, te = MM.FlightSeconds, MM.TravelNodes, MM.TravelEdges
		if not fs then return false, "no measured flight times loaded" end
		local nodes, hops = 0, 0
		for _, nb in pairs(fs) do
			nodes = nodes + 1
			for _ in pairs(nb) do hops = hops + 1 end
		end
		if hops < 3000 then
			return false, ("only %d flight hops loaded"):format(hops)
		end
		-- Every hop must be reachable by name, or the pathfinder cannot use it.
		local named = MM.FlightNodeName
		if not named then return false, "no nodeID->name map" end
		local projected = 0
		for id, nb in pairs(fs) do
			if named[id] then
				for oid in pairs(nb) do if named[oid] then projected = projected + 1 end end
			end
		end
		if projected < hops then
			return false, ("%d of %d hops have no name mapping"):format(hops - projected, hops)
		end
		if not (tn and te) then return false, "no travel network loaded" end
		local n, e = 0, 0
		for _ in pairs(tn) do n = n + 1 end
		for _ in pairs(te) do e = e + 1 end
		if n < 500 or e < 150 then
			return false, ("travel network thin: %d nodes, %d edges"):format(n, e)
		end
		return true, ("%d nodes / %d hops, all projected; network %d/%d"):format(
			nodes, hops, n, e)
	end)

	check("Every measured hop actually reaches the pathfinder", function()
		-- The check above proves the DATA is complete. This proves the ROUTER
		-- received it, which is a different claim and the one that kept being
		-- false: 4,068 hops loaded, named and verified, while the graph quietly
		-- discarded 903 of them because it had no coordinates for one end.
		--
		-- A number that only counts what survived cannot notice what did not.
		local J, fs, named = MM.Journey, MM.FlightSeconds, MM.FlightNodeName
		if not (J and J.Stats and fs and named) then
			return nil, "travel layer not loaded yet"
		end
		J.Stats() -- forces the graph to build
		local hops = 0
		for _, nb in pairs(fs) do for _ in pairs(nb) do hops = hops + 1 end end
		if hops == 0 then return false, "no measured hops to feed it" end
		local got = J.measuredEdges or 0
		if got == 0 then
			return false, "the graph took none of the measured flight times"
		end
		-- Same-name collisions are genuine duplicates and are expected to merge;
		-- anything beyond a small fraction is data being dropped, not deduped.
		local lost = hops - got
		if lost > hops * 0.05 then
			return false, ("%d of %d measured hops never reached the graph"):format(
				lost, hops)
		end
		return true, ("%d of %d hops routable, %d transit-only nodes"):format(
			got, hops, J.transitNodes or 0)
	end)

	check("A place we ship a flight point for is not an unknown place", function()
		-- A Hearthstone binds to an INN, and an inn is not a map, so a bind in
		-- "Har'athir" resolved to nothing and the addon asked the player to go
		-- and stand there. We already shipped that name -- a flight master in
		-- Harandar -- and nothing was looking at it.
		local U = MM.Util
		if not (U and U.ResolveMapByName and MM.FlightPointMapForName) then
			return nil, "no flight point index"
		end
		local id = MM.FlightPointMapForName("Har'athir")
		if not id then return nil, "that flight point is not in this build" end
		local viaResolve = U.ResolveMapByName("Har'athir")
		if viaResolve ~= id then
			return false, ("flight point says map %s, resolver says %s")
				:format(tostring(id), tostring(viaResolve))
		end
		-- A real map name must still win outright.
		local org = U.ResolveMapByName("Orgrimmar")
		if not org then return false, "a real map name stopped resolving" end
		return true, ("Har'athir -> map %d, and map names still win"):format(id)
	end)

	check("A zone name resolves whatever its case", function()
		-- The travel graph keys its zones lowercase and then asked the map
		-- index, which the client fills with proper-cased names. Every one of
		-- those lookups missed, and missed quietly: cross-zone distances fell
		-- back to a crude zone-size approximation, and any zone without a flight
		-- point of its own became unreachable -- "no way to reach the forbidden
		-- reach from the graph" while 2,180 positioned nodes sat loaded.
		--
		-- A resolver that only answers in one case is a resolver half its
		-- callers cannot use.
		local U = MM.Util
		if not (U and U.ResolveMapByName) then return nil, "resolver missing" end
		local probes, checked, bad = { "Orgrimmar", "Stormwind City", "Tanaris" }, 0, nil
		for _, name in ipairs(probes) do
			local exact = U.ResolveMapByName(name)
			if exact then
				checked = checked + 1
				local lower = U.ResolveMapByName(name:lower())
				local upper = U.ResolveMapByName(name:upper())
				if lower ~= exact or upper ~= exact then
					bad = bad or ("%s: exact %s, lower %s, upper %s"):format(
						name, tostring(exact), tostring(lower), tostring(upper))
				end
			end
		end
		if checked == 0 then return nil, "no probe zone resolved at all" end
		if bad then return false, bad end
		return true, ("%d zones resolve identically in any case"):format(checked)
	end)

	check("A zone name resolves whether or not it starts with 'The'", function()
		-- The client is not consistent about the article and neither is anyone
		-- writing a zone down. The continent is "The Shadowlands"; the item that
		-- goes there is a "Wormhole Generator: Shadowlands", and it was offered
		-- to nobody because "this client has no map called Shadowlands" -- which
		-- was true, and the wrong question.
		--
		-- Every "The X" the client ships is asked for without its article. The
		-- answer has to be a map of that same name; anything else would be the
		-- fallback reaching past a real match, which is the only way this could
		-- do harm.
		local U = MM.Util
		if not (U and U.ResolveMapByName) then return nil, "resolver missing" end
		local asked, bad = 0, {}
		local seen = {}
		for mapID = 1, 3000 do
			local info = C_Map.GetMapInfo(mapID)
			local bare = info and info.name and info.name:match("^The%s+(.+)$")
			if bare and not seen[bare] then
				seen[bare] = true
				asked = asked + 1
				local got = U.ResolveMapByName(bare)
				local name = got and C_Map.GetMapInfo(got)
				name = name and name.name
				if name ~= info.name and name ~= bare then
					if #bad < 4 then
						bad[#bad + 1] = ("%s -> %s"):format(bare, tostring(name))
					end
				end
			end
		end
		if asked == 0 then return nil, "no articled map name on this client" end
		if #bad > 0 then
			return false, ("%d of %d articled names miss: %s"):format(
				#bad, asked, table.concat(bad, ", "))
		end
		return true, ("%d articled zone name(s) resolve without their article"):format(asked)
	end)

	check("Nowhere on another continent is ever called nearby", function()
		-- The bug this exists for: a world position is CONTINENT-RELATIVE, so
		-- differencing two of them across continents is not a distance. It is
		-- two unrelated numbers subtracted, and it comes out small. The graph
		-- duly decided Bastion was six seconds from The Maw, routed a Tazavesh
		-- goal to a raid door in Nazmir, and collapsed every profile to the
		-- same three minutes -- all of it stated with total confidence.
		--
		-- Cheap wrong answers are the dangerous kind: they win the comparison.
		local J = MM.Journey
		if not (J and J.AttachAudit) then return nil, "travel layer not loaded" end
		local probes = { "Tazavesh, the Veiled Market", "Ny'alotha, the Waking City",
			"The Forbidden Reach", "Sanctum of Domination" }
		for _, zone in ipairs(probes) do
			-- The audit reads what attaching actually chose, so something has to
			-- have attached first. Without this the check reports "nothing to
			-- audit" forever, which is a pass that proves nothing.
			local mapID = MM.Util and MM.Util.ResolveMapByName
				and MM.Util.ResolveMapByName(zone)
			if J.Plan then J.Plan(zone, 50, 50, "Orgrimmar", 50, 50, nil, mapID) end
		end
		-- THE PROBES ONLY GUARANTEE THERE IS SOMETHING TO READ.
		--
		-- What gets audited is every cross-zone attachment made this session,
		-- the player's own route included. Naming four zones tested those four
		-- and quietly reported the number as if it were coverage; a zone nobody
		-- thought to name is exactly where this goes wrong, because the fallback
		-- only runs where a zone has no nodes of its own.
		if not J.AttachAuditAll then return nil, "travel layer predates this audit" end
		local audited, offending, worst = J.AttachAuditAll()
		if audited == 0 then return nil, "nothing has attached across zones yet" end
		if offending > 0 then
			return false, ("%d attachment(s) sit on another continent, worst: %s")
				:format(offending, worst or "?")
		end
		return true, ("%d cross-zone attachment(s) audited, every entry point on "
			.. "the right continent"):format(audited)
	end)

	check("A plan is torn down after it is made", function()
		-- START and GOAL are temporary nodes wired into the graph for one plan
		-- and removed after. The removal walked the destination zone's own
		-- nodes, so whenever the goal attached through the nearest-elsewhere
		-- fallback -- whose entry points are by definition NOT in that zone --
		-- its "-> GOAL" edges were left behind for the rest of the session.
		--
		-- Every later plan then found a stale cheap edge to a goal that no
		-- longer existed. Four legs with different origins and destinations came
		-- back with the same route and the same 1.7 minutes, and a destination
		-- with 21 entry points of its own still ended at a node in the Emerald
		-- Dream. Nothing errored; the graph was answering an older question.
		local J = MM.Journey
		if not (J and J.Plan and J.Node) then return nil, "travel layer not loaded" end
		-- Two destinations far apart, planned from the same origin. If a leftover
		-- goal edge survives the first, the second will reuse it and agree.
		local pairs_ = {
			{ "Orgrimmar", "Tanaris" },
			{ "Orgrimmar", "The Maw" },
		}
		local seen, costs = {}, {}
		for i, pr in ipairs(pairs_) do
			local mins, legs = J.Plan(pr[1], 50, 50, pr[2], 50, 50)
			if not mins then return nil, ("could not plan %s -> %s"):format(pr[1], pr[2]) end
			costs[i] = mins
			seen[i] = (J.Describe and J.Describe(legs)) or tostring(mins)
		end
		if seen[1] == seen[2] then
			return false, ("two different destinations returned the same route: %s")
				:format((seen[1] or ""):sub(1, 80))
		end
		return true, ("%d destinations, %d distinct routes (%.1fm vs %.1fm)")
			:format(#pairs_, 2, costs[1], costs[2])
	end)

	check("The broker gives a display addon everything it needs", function()
		-- Titan, Bazooka and ElvUI datatexts all build their plugin from this
		-- one object, and every field they cannot find degrades silently: a
		-- missing label reads as "MasterMounts" jammed together in the top bar,
		-- and a tocname that does not name the addon FOLDER makes Titan's
		-- category, version and notes lookups all return nil.
		--
		-- That last one is the folder-name mismatch that once broke four
		-- textures without erroring. It is checkable, so check it.
		local LDB = LibStub and LibStub:GetLibrary("LibDataBroker-1.1", true)
		if not LDB then return nil, "LibDataBroker not loaded" end
		local obj
		for name, dataobj in LDB:DataObjectIterator() do
			if name == "MasterMounts" then obj = dataobj break end
		end
		if not obj then return false, "no data object registered" end

		if obj.type ~= "data source" then
			return false, ("type is %q, which no display addon maps"):format(tostring(obj.type))
		end
		for _, field in ipairs({ "text", "icon", "label", "tocname" }) do
			if obj[field] == nil or obj[field] == "" then
				return false, "the broker carries no " .. field
			end
		end
		if type(obj.OnClick) ~= "function" or type(obj.OnTooltipShow) ~= "function" then
			return false, "the broker has no click or tooltip handler"
		end

		-- The real test: does tocname actually name an addon this client has?
		local meta = C_AddOns and C_AddOns.GetAddOnMetadata
		if not meta then return nil, "no addon metadata API on this client" end
		local version = meta(obj.tocname, "Version")
		if not version then
			return false, ("tocname %q is not an addon folder on this client, so "
				.. "Titan resolves no category or version"):format(tostring(obj.tocname))
		end
		if version ~= MM.VERSION then
			return false, ("the toc says %s and MM.VERSION says %s"):format(
				version, tostring(MM.VERSION))
		end
		local category = meta(obj.tocname, "X-Category")
		return true, ("data source, label %q, resolves to %s v%s, category %s")
			:format(obj.label, obj.tocname, version, tostring(category or "none"))
	end)

	check("A price you can already pay is not a grind", function()
		-- 86 vendor mounts state a gold price in prose and carried no cost the
		-- planner could read, so each fell to its category's effort rating: a
		-- ten gold ram ranked like a raid. Gold is not a duration, so what is
		-- checked is affordability, which the client answers exactly.
		--
		-- The failure this guards is silence. If the overrides stop loading, or
		-- GetMoney stops answering, nothing errors -- the mounts simply drift
		-- back to being costed like grinds and the order quietly worsens.
		local db = MM.DBByName
		if not db then return nil, "database not loaded" end
		local priced, cheapest, dearest = 0, nil, nil
		for _, rec in pairs(db) do
			if rec.goldCost then
				priced = priced + 1
				if rec.goldCost <= 0 then
					return false, (rec.name or "?") .. " has a goldCost of zero"
				end
				cheapest = math.min(cheapest or rec.goldCost, rec.goldCost)
				dearest = math.max(dearest or rec.goldCost, rec.goldCost)
			end
		end
		if priced == 0 then return false, "no gold prices loaded at all" end
		if not GetMoney then return nil, "this client has no GetMoney" end

		-- And it must actually reach the estimate. A number in the record that
		-- no cost term reads is the shape of bug this suite keeps missing.
		local sample, entry
		for _, rec in pairs(db) do
			if rec.goldCost and rec.goldCost * 10000 <= (GetMoney() or 0) then
				local e = rec.spellID and MM.Scanner and MM.Scanner.bySpell
					and MM.Scanner.bySpell[rec.spellID]
				if e then sample, entry = rec, e break end
			end
		end
		if not entry then
			return true, ("%d priced %sg-%sg; none affordable and scanned right now")
				:format(priced, U.Comma(cheapest), U.Comma(dearest))
		end
		local _, parts = MM.Planner.TimeCommitment(entry)
		if parts then
			for _, part in ipairs(parts) do
				if (part.label or ""):find("already have") then
					return true, ("%d priced %sg-%sg; %s costs %d min, not %d")
						:format(priced, U.Comma(cheapest), U.Comma(dearest),
							sample.name, part.minutes, 240)
				end
			end
			return false, (sample.name or "?")
				.. " is affordable but no cost term says so"
		end
		return true, ("%d gold prices loaded, %sg to %sg"):format(
			priced, U.Comma(cheapest), U.Comma(dearest))
	end)

	check("The coverage metric agrees with what the planner actually does", function()
		-- THE CLASS OF BUG THIS EXISTS FOR, and it is not hypothetical: 86
		-- records were given a real gold price, the planner priced them from it,
		-- and the coverage report went on calling them bare -- still offering
		-- points for fixing something already fixed. Nothing failed. The metric
		-- had simply never been told about a whole kind of cost.
		--
		-- A count that is not checked against the behaviour it claims to
		-- describe is a number that drifts. So: whatever CostCoverage calls
		-- MODELLED must actually receive a real cost term, and whatever it calls
		-- BARE must not. Disagreement in either direction is the metric lying.
		local CO, P = MM.Contribute, MM.Planner
		if not (CO and CO.CostCoverage and P and P.TimeCommitment) then
			return nil, "coverage or planner not loaded"
		end
		-- The labels the planner uses when it has nothing real to go on.
		local FALLBACK = {
			["cost not yet modelled"] = true,
			["achievement not yet modelled"] = true,
		}
		local function isFallback(label)
			return FALLBACK[label] or (label or ""):find("^effort rating ") ~= nil
		end

		local byCat = CO.CostCoverage()
		local bare = {}
		for _, d in pairs(byCat) do
			for _, name in ipairs(d.bare or {}) do bare[name] = true end
		end

		local checkedModelled, checkedBare, wrong = 0, 0, nil
		for _, entry in ipairs(MM.Scanner and MM.Scanner.mounts or {}) do
			local rec = entry.rec
			if rec and rec.obtainable and MM.PLANNABLE[rec.category] and not entry.collected then
				local ok, _, parts = pcall(P.TimeCommitment, entry)
				if ok and parts then
					local real = false
					for _, part in ipairs(parts) do
						if not isFallback(part.label) then real = true break end
					end
					if bare[rec.name] then
						-- ONE DIRECTION ONLY, and this is the reason.
						--
						-- "Bare" means we cannot price the ACQUISITION -- no drop
						-- rate, no stated cost, no chain. The planner may still
						-- charge real minutes for the VISIT, and should: an
						-- untimed treasure in Zul'Aman is a run you have to make
						-- whether or not anyone knows the odds. Asserting the
						-- reverse called that a contradiction when it is correct
						-- behaviour, and Ancestral War Bear said so.
						checkedBare = checkedBare + 1
					else
						checkedModelled = checkedModelled + 1
						if not real and not wrong then
							wrong = ("%s is counted modelled but gets only a fallback")
								:format(rec.name)
						end
					end
				end
			end
		end
		if checkedModelled + checkedBare == 0 then
			return nil, "no plannable uncollected records to compare"
		end
		if wrong then return false, wrong end
		return true, ("%d counted modelled all get a real cost term (%d bare, "
			.. "which may still cost a visit)"):format(checkedModelled, checkedBare)
	end)

	check("Nothing we do is refused as a protected action", function()
		-- Reported from a delve: the arrow's action button is a secure frame,
		-- and Show and Hide on one are refused in combat exactly as SetAttribute
		-- is. The attribute writes were guarded from the start; the visibility
		-- was not. A delve is wall-to-wall combat and the arrow re-evaluates on
		-- every step of the route, so the one missing guard filled a chat frame.
		--
		-- ASKED OF THE SESSION, NOT OF A FIXTURE. Whether a call is refused
		-- depends on combat, on what else is tainted, and on the client's own
		-- rules -- none of which a test can stage honestly. What it can do is
		-- read what the client actually refused while somebody played, which is
		-- the only evidence that ever mattered, and stop it living solely in a
		-- chat frame the player has to happen to notice.
		local list = MM.blockedCalls
		if not list then return nil, "the protected-call capture is not loaded" end
		if #list > 0 then
			local first = list[1]
			return false, ("%d protected call(s) refused this session, first: %s %s")
				:format(#list, first.kind, first.func)
		end
		return true, "no forbidden or blocked call this session"
	end)

	check("A command that breaks says so", function()
		-- /mm routertest 300 printed "Modelling the router..." and then nothing.
		-- No window, no output, no error. MM:Fire pcalls every handler and sent
		-- the failure to geterrorhandler(), which retail hides by default -- so
		-- the command looked like it simply did nothing.
		--
		-- Silence is the worst failure mode a diagnostic can have, and this file
		-- has spent a day proving it. The dispatcher must speak.
		local seen
		local realPrint = MM.Print
		MM.Print = function(_, fmt, ...) seen = tostring(fmt or ""):format(...) end
		MM:On("MM_SELFTEST_FIRE_PROBE", function() error("deliberate", 0) end)
		local realHandler = geterrorhandler
		-- Swallow the error the dispatcher forwards; we are testing the CHAT
		-- line, and this probe throws on purpose.
		geterrorhandler = function() return function() end end
		local ok = pcall(function() MM:Fire("MM_SELFTEST_FIRE_PROBE") end)
		geterrorhandler = realHandler
		MM.Print = realPrint

		if not ok then return false, "Fire let a handler error escape" end
		if not seen then
			return false, "a handler threw and the dispatcher said nothing in chat"
		end
		if not seen:find("MM_SELFTEST_FIRE_PROBE", 1, true) then
			return false, "the failure line does not name the command: " .. seen:sub(1, 60)
		end
		return true, "a thrown handler is reported in chat, naming the command"
	end)

	check("The easiest ten are ten you could start today", function()
		-- Reported from play twice. First the Timewalking week was misdetected,
		-- so mounts whose badges were already paid for read as ready. Once that
		-- was right they were STILL offered, because the easiest list ranked on
		-- ease alone and never asked whether the work could be started -- and a
		-- paid-for badge mount scores as an easy pickup whether or not there is
		-- a Timewalking week to spend it in.
		--
		-- BLOCKED is the definition Urgency already uses: a lockout, an event
		-- that is not on, a rotation that has moved, a prerequisite, something
		-- unobtainable. A currency grind is NOT blocked and must stay eligible,
		-- because you can go and progress one today.
		local P = MM.Planner
		if not (P and P.Easiest and P.Urgency and P.URGENCY) then
			return nil, "planner not loaded"
		end
		local picks = P:Easiest(10)
		if #picks == 0 then return nil, "nothing plannable outside the plan" end
		local offered = {}
		for _, entry in ipairs(picks) do
			local urgency, why = P.Urgency(entry)
			if urgency == P.URGENCY.BLOCKED then
				offered[#offered + 1] = ("%s (%s)"):format(
					tostring(entry.name), tostring(why))
			end
		end
		if #offered > 0 then
			return false, ("%d of %d offered goals cannot be acted on: %s")
				:format(#offered, #picks, table.concat(offered, "; "))
		end
		return true, ("%d offered, every one of them startable now"):format(#picks)
	end)

	check("Easiest means easiest, not whatever you put first", function()
		-- The easiest list ranked tiers by the player's OWN priority order, so it
		-- returned "the highest in the order you already set" and called it
		-- difficulty. Put INSTANCE first and a vendor mount you could buy
		-- standing still ranked below a dungeon.
		--
		-- Reordering priorities must move the ROUTE and must not move this.
		local P, W = MM.Planner, MM.Weights
		if not (P and P.Easiest and W and W.Order and W.Move) then
			return nil, "planner or weights not loaded"
		end
		local before = P:Easiest(10)
		-- ONE IS ENOUGH TO CHECK.
		--
		-- This demanded three, which meant that with a full plan -- 286 goals,
		-- almost everything already added -- the pool was never big enough and
		-- the check has never run once. The threshold was arbitrary: with one
		-- candidate "the easiest" is that candidate and reordering priorities
		-- must still return it; with two the comparison is already real. Only
		-- an EMPTY pool has nothing to say.
		if #before < 1 then return nil, "nothing plannable outside the plan" end
		local function names(list)
			local out = {}
			for _, e in ipairs(list) do out[#out + 1] = e.name or "?" end
			return table.concat(out, "|")
		end
		local was = names(before)

		-- W.Move is the only way the order changes, so use it -- a test that
		-- writes the store directly would not prove the real path is safe.
		local depth = #W.Order() - 1
		for i = 1, depth do W.Move(i, 1) end          -- rotate the whole order
		local after = names(P:Easiest(10))
		for i = depth, 1, -1 do W.Move(i, 1) end      -- and rotate it back
		local restored = names(P:Easiest(10))
		local saved = W.Order()

		if was ~= after then
			return false, "rotating your priority order changed the EASIEST list"
		end
		if restored ~= was then
			return false, "restoring the order did not restore the list"
		end
		return true, ("%d easiest, unchanged by rotating all %d priorities")
			:format(#before, #saved)
	end)

	check("The plan follows you between characters", function()
		-- The router can report that another character already qualifies, and
		-- following that meant logging into a character with an EMPTY plan and
		-- no idea where you were: the plan lived in per-character saved data while
		-- the collection it plans for is account-wide.
		--
		-- Two properties, and the second is the one that is easy to get wrong.
		local plan, db, cdb = MM.Planner, MM.db, MM.cdb
		if not (plan and db and cdb) then return nil, "databases not ready" end

		-- 1. One list, not two. The character table must BE the account table,
		--    or a switch silently starts over.
		if cdb.plan ~= db.plan then
			return false, "the character plan is a separate table from the account plan"
		end

		-- 2. The resume anchor is an identity, not a position. A route is
		--    rebuilt per character, so an index means something different on
		--    each one -- carrying it lands you at somebody else's stop.
		if db.routeGoal ~= nil and type(db.routeGoal) ~= "number" then
			return false, "the resume anchor is not a spellID"
		end
		local R = MM.Router
		if R and R.route and #R.route > 0 and cdb.routeActive then
			local cur = R:Current()
			if cur and db.routeGoal then
				local id = cur.spellID
					or (cur.members and cur.members[1] and cur.members[1].spellID)
				if id and db.routeGoal ~= id then
					return false, "the anchor does not match the current goal"
				end
			end
		end
		return true, ("%d goals in one account-wide plan%s"):format(#db.plan,
			db.routeGoal and (", resuming at " .. tostring(db.routeGoal)) or "")
	end)

	check("An instance can be entered, not only left", function()
		-- Links with an end marked "inside" were skipped entirely, to avoid
		-- planting a node at a map's corner. Those links are the DOORS -- the
		-- only edges joining the outdoor world to an instance -- so a goal in
		-- Tazavesh could be routed FROM and never TO. Both directions existed,
		-- so no count noticed; the travel report said it outright, with 14 goal
		-- attachments and no path.
		--
		-- Asymmetry is the thing to assert. A place you can leave and cannot
		-- reach is not a routing preference, it is a hole.
		local J = MM.Journey
		if not (J and J.Plan and MM.Util) then return nil, "travel layer not loaded" end
		local probes = { "Tazavesh, the Veiled Market", "Ny'alotha, the Waking City" }
		local hub = "Orgrimmar"
		local checked, oneWay = 0, nil
		for _, zone in ipairs(probes) do
			local mapID = MM.Util.ResolveMapByName and MM.Util.ResolveMapByName(zone)
			local out = J.Plan(zone, 50, 50, hub, 50, 50, nil, mapID)
			local back = J.Plan(hub, 50, 50, zone, 50, 50, nil, nil, mapID)
			if out or back then
				checked = checked + 1
				if out and not back then
					oneWay = oneWay or (zone .. " can be left but not reached")
				end
			end
		end
		if checked == 0 then return nil, "no probe instance is routable at all yet" end
		if oneWay then return false, oneWay end
		return true, ("%d instances reachable in both directions"):format(checked)
	end)

	check("Most of the world is reachable from one place", function()
		-- THE CHECK NOTHING WAS DOING. Node and edge counts were healthy while
		-- the graph was 115 separate islands whose largest held 94 nodes -- 7.9%
		-- of it -- so the router could not plan most legs and silently charged a
		-- flat constant instead. Every count looked fine because every count was
		-- fine; the connectivity was not.
		local J = MM.Journey
		if not (J and J.Reachable) then return nil, "travel layer not loaded" end
		local from
		for _, hub in ipairs({ "Orgrimmar", "Stormwind City", "Valdrakken", "Dornogal" }) do
			if J.Node and J.Node(hub) then from = hub break end
		end
		if not from then return nil, "no known hub in the graph to start from" end
		local reached, total = J.Reachable(from)
		if not (reached and total and total > 0) then
			return false, "the graph could not be walked at all"
		end
		local pct = reached / total * 100
		if pct < 50 then
			return false, ("only %.0f%% of the graph is reachable from %s -- "
				.. "the rest is islands the router cannot plan through")
				:format(pct, from)
		end
		return true, ("%.0f%% reachable from %s (%d of %d nodes), %d transit links")
			:format(pct, from, reached, total, J.transitLinks or 0)
	end)

	check("An island with no door is bridged, or says why not", function()
		-- 47 zones had no edge into the graph AT ALL -- the Forbidden Reach
		-- among them, which is why a Sanctum of Domination goal reported
		-- "no path" and fell through to a flat 6.3-minute flight between two
		-- continents. Counting links could never see it: all 1,496 were real.
		--
		-- This asserts the RELATIONSHIP the fix depends on: after bridging,
		-- what is left unbridged is only what has nothing on its continent to
		-- fly from -- never something we simply failed to try.
		local J = MM.Journey
		if not (J and J.Reachable) then return nil, "travel layer not loaded" end
		J.Stats()      -- force the build, so the counters below exist
		if J.components == nil then return nil, "no component walk recorded" end
		local made, skipped = J.bridges or 0, J.bridgeSkipped or 0
		if J.components > 1 and made == 0 and skipped == 0 then
			return false, ("%d components and not one was even considered")
				:format(J.components)
		end
		-- Every bridge must be priced at or above the floor.
		--
		-- This fired for real: Raven Hill's flight point sits on exactly the
		-- same ground as a Raven Hill transit endpoint in another component,
		-- so the bridge measured 0 yards and cost 0 seconds. A free edge is
		-- teleportation -- Dijkstra pops both components at the same distance
		-- and the search stopped finishing inside the frame budget. Same
		-- ground still costs something to cross, and BorderData already priced
		-- that at 5 seconds.
		local FLOOR = 5 / 60      -- minutes; matches MIN_BRIDGE_SECONDS
		for _, b in ipairs(J.bridgeDetail or {}) do
			if not (b.minutes and b.minutes >= FLOOR - 1e-9) then
				return false, ("bridge %s -> %s costs %s min -- below the floor, "
					.. "and a free edge is teleportation")
					:format(tostring(b.from), tostring(b.to), tostring(b.minutes))
			end
		end
		return true, ("%d components, %d of them hold a goal: %d bridged by "
			.. "flying, %d left alone; %d nearest-point lookups%s")
			:format(J.components, J.bridgeRelevant or 0, made, skipped,
				J.bridgeCompares or 0,
				J.bridgeWhy and (" -- " .. J.bridgeWhy) or "")
	end)

	check("Every dungeon teleport id is a teleport", function()
		-- One shipped id was not. 393272 rode in from a third-party export as
		-- the Algeth'ar Academy teleport and is an internal test spell in the
		-- client's own table, so IsPlayerSpell was false for everyone and that
		-- teleport had never been offered. Nothing errored; the route was just
		-- quietly longer.
		--
		-- The client cannot be asked "is this a teleport", but it can be asked
		-- whether a spell EXISTS and what it is called -- and a name that does
		-- not begin with the family's prefix is the shape that failure took.
		local list = MM.DungeonTeleports
		if not (list and #list > 0) then return false, "no dungeon teleports loaded" end
		local named, unknown, odd = 0, 0, nil
		for _, t in ipairs(list) do
			local nm = C_Spell and C_Spell.GetSpellName and C_Spell.GetSpellName(t.spell)
			nm = nm and MM.Util.ReadableString(nm)
			if not nm then
				unknown = unknown + 1
			else
				named = named + 1
				-- The data's own name and the client's should agree.
				if nm ~= t.name and not odd then
					odd = ("%d is %q here and %q in the client"):format(t.spell, t.name, nm)
				end
			end
		end
		if odd then return false, odd end
		if named == 0 then return nil, "the client named none of them yet" end
		return true, ("%d of %d ids name themselves exactly as shipped; %d not "
			.. "cached yet"):format(named, #list, unknown)
	end)

	check("A teleport you have not earned is never offered", function()
		-- 76 dungeon teleports joined the option list, and almost nobody has
		-- most of them. If ownership were assumed rather than asked, every route
		-- would be planned around instant travel this character cannot do --
		-- which is worse than no modelling at all, because it looks authoritative.
		local list, TP = MM.DungeonTeleports, MM.Teleports
		if not (list and TP and TP.Options) then return nil, "teleports not loaded" end
		local total = 0
		for _, t in ipairs(list) do
			total = total + 1
			if not t.spell then
				return false, (t.name or "?") .. " has no spell id to check ownership with"
			end
		end
		if total == 0 then return false, "no dungeon teleports loaded" end
		-- Ask the CLIENT, through whichever call this build actually has.
		--
		-- The bare IsSpellKnown is a deprecated shim over C_SpellBook and it
		-- raises rather than returning nil when handed anything but a number --
		-- which is what this check did on its first run, because a landing
		-- carries no spell id and the nil went straight through. Guarded and
		-- pcall'd: a check that cannot read ownership must say so, not explode
		-- and not quietly pass.
		local function knowsSpell(id)
			if type(id) ~= "number" then return nil end
			if C_SpellBook and C_SpellBook.IsSpellKnown then
				local ok, res = pcall(C_SpellBook.IsSpellKnown, id)
				if ok then return res end
			end
			if IsPlayerSpell then
				local ok, res = pcall(IsPlayerSpell, id)
				if ok then return res end
			end
			return nil
		end
		local offered, wrong, unreadable = 0, nil, 0
		for _, o in ipairs(TP.Options() or {}) do
			if type(o.key) == "string" and o.key:find("^dungeontp_") then
				offered = offered + 1
				local known = knowsSpell(o.spell)
				if known == nil then unreadable = unreadable + 1
				elseif not known then wrong = wrong or o.name end
			end
		end
		if unreadable > 0 then
			return nil, ("%d offered teleports carry no readable spell id"):format(unreadable)
		end
		if wrong then
			return false, ("%s is offered but this character does not know it"):format(wrong)
		end
		return true, ("%d known of %d dungeon teleports, all verified with the client")
			:format(offered, total)
	end)

	check("Portals and ships are priced, and never for free", function()
		-- A zero-cost edge is teleportation to a shortest-path search: it will
		-- chain a dozen of them across the world and report the trip as instant.
		local links, secs = MM.TransitLinks, MM.TransitSeconds
		if not (links and secs) then return nil, "transit links not loaded" end
		local n, modes = 0, {}
		for _, L in ipairs(links) do
			n = n + 1
			local cost = secs[L.mode]
			if not cost or cost <= 0 then
				return false, ("%s -> %s (%s) costs nothing"):format(L.a, L.b, L.mode)
			end
			modes[L.mode] = true
			-- 0,0 means "inside the instance". If one ever arrives as a real
			-- position the graph will measure distances to a map corner.
			if L.ainside and (L.ax ~= 0 or L.ay ~= 0) then
				return false, L.a .. " is marked inside but carries a position"
			end
		end
		if n == 0 then return false, "no transit links loaded" end
		local kinds = 0
		for _ in pairs(modes) do kinds = kinds + 1 end
		return true, ("%d links across %d modes, all priced"):format(n, kinds)
	end)

	check("A node with no position is never asked for one", function()
		-- Transit-only nodes carry measured times but no x,y. That is safe for
		-- exactly one reason: the three places that read a coordinate all walk
		-- byZone, never the graph. If a positionless node ever lands in byZone
		-- the distance maths gets nil and the route dies mid-plan -- so assert
		-- the separation rather than trusting it to stay true.
		local J = MM.Journey
		if not (J and J.NodesByZone) then return nil, "graph not inspectable" end
		local zones = J.NodesByZone()
		if not zones then return nil, "graph not built yet" end
		local checked, bad = 0, nil
		for _, names in pairs(zones) do
			for _, n in ipairs(names) do
				local node = J.Node and J.Node(n)
				if node then
					checked = checked + 1
					if not (node.x and node.y) then bad = bad or node.name end
				end
			end
		end
		if checked == 0 then return nil, "no positioned nodes to check" end
		if bad then
			return false, ("%s is used for distance but has no position"):format(bad)
		end
		return true, ("%d positioned nodes, none of them placeless"):format(checked)
	end)

	check("Network legs are priced, and never for free", function()
		-- A portal is fast, not instant. An edge that costs nothing makes the
		-- router teleport through the world at zero charge and reorder the whole
		-- plan around a leg that does not exist.
		local NW = MM.Network
		if not (NW and NW.EdgeSeconds and MM.TravelEdges) then
			return nil, "network module absent"
		end
		local zero, worst = 0, nil
		for _, e in ipairs(MM.TravelEdges) do
			local s = NW.EdgeSeconds(e)
			if not s or s <= 0 then
				zero = zero + 1
				worst = worst or ((e.method or "?") .. ": " .. (e.from or "?"))
			end
		end
		if zero > 0 then
			return false, ("%d edges priced at zero, e.g. %s"):format(zero, worst)
		end
		return true, ("%d edges, all priced"):format(#MM.TravelEdges)
	end)

	check("A session's promise reaches the route", function()
		-- ASSERT THE PROMISE, NOT THE SELECTION.
		--
		-- Three earlier versions compared the route against a re-run of S.Fit --
		-- by identity, then by set. Both are unstable by construction: Fit is a
		-- greedy whose ties break on pool ORDER, and ApplySession changes that
		-- order, so the second run may legitimately choose a different stop than
		-- the first. The test failed four times and the feature was fine for the
		-- last two.
		--
		-- What a session actually promises is simpler and does not care which
		-- stops were picked: the work at the front of the route FITS THE CLOCK.
		-- That is stable, it is the thing a player would notice breaking, and it
		-- needs no second opinion from the selector.
		local S, R = MM.Session, MM.Router
		if not (S and S.Start and S.Stop and R) then return false, "session mode missing" end
		if not R.route or #R.route == 0 then return nil, "no route built to constrain" end

		local wasActive = MM.cdb.routeActive
		local prevIndex = MM.cdb.routeIndex
		local restore = S.Active and S.Active()
		local len = S.LENGTHS[2] or S.LENGTHS[1]

		S.Start(len.minutes, true)  -- setOnly: must not launch a route
		local st = S.Active and S.Active()
		local planned = st and st.planned or 0

		-- MEASURE FIRST. travelMinutes is populated by R.Measure(), which runs
		-- AFTER Build -- so reading it straight after Start summed zero and then
		-- compared zero against a budget. A check that passes because it added
		-- up nothing is worse than one that fails.
		if R.Measure then R.Measure() end

		local total, counted, priced = 0, 0, 0
		if planned > 0 then
			for i = 1, math.min(planned, #R.route) do
				local stop = R.route[i]
				local travel = stop.travelMinutes or 0
				-- VISIT minutes, because that is what S.Fit spends.
				--
				-- This used workMinutes -- EffortMinutes, the whole grind. A
				-- five-mount island stop is one five-minute run, but its grind
				-- is 40 attempts, so the check totalled 732 minutes for a
				-- 45-minute session and failed a session that was correct. The
				-- test has to cost the route the way the session costed it or
				-- it is measuring a different question.
				local work = stop.visitMinutes or stop.workMinutes or 15
				if stop.travelMinutes or stop.visitMinutes or stop.workMinutes then
					priced = priced + 1
				end
				total = total + travel + work
				counted = counted + 1
			end
		end

		-- Restore before reporting: Start and Stop each rebuild the route, and
		-- the checks after this one must not inherit our leftovers.
		S.Stop(true)
		MM.cdb.routeActive = wasActive
		MM.cdb.routeIndex = prevIndex or 1
		if restore then S.Start(restore.minutes, true) end
		if R.Build then R:BuildSync() end

		if planned == 0 then return nil, "nothing fits the sample length" end
		if counted < planned then
			return false, ("session claims %d stops, route has %d"):format(planned, counted)
		end
		-- The sum has to come from somewhere. If not one leading stop carries a
		-- real cost, this check is adding up defaults and proving nothing --
		-- which is exactly how it passed at "0 min of work".
		if priced == 0 then
			return false, ("%d leading stops carry no measured cost at all"):format(counted)
		end
		-- One stop may overrun on its own -- Fit takes the first thing that fits
		-- and a single long run can exceed the budget by itself. The promise is
		-- about the SET, so allow the last stop to spill.
		if counted > 1 and total > len.minutes * 2 then
			return false, ("%d leading stops total %.0f min for a %d min session")
				:format(counted, total, len.minutes)
		end
		return true, ("%d min -> %d stops leading, %.0f min total (%d priced)"):format(
			len.minutes, counted, total, priced)
	end)

	check("A session promise is kept", function()
		-- "we have 45 minutes" is a promise, not a lean. What the mode offers
		-- must be measured with the SAME clock the router uses, or the addon
		-- promises one thing and routes another.
		local S = MM.Session
		if not (S and S.Fit) then return false, "session mode missing" end
		-- EACH LENGTH FITTED ONCE.
		--
		-- This asked S.Fit for the same four lengths twice over and then twice
		-- more for the numbers it prints -- ten packs of an 86-stop route to
		-- answer four questions. Nothing about a fit changes between those
		-- calls, and it made this the slowest check in the suite until one
		-- extra routable goal pushed it past its own 1,200 ms bar. The bar was
		-- right; the check was doing the work two and a half times.
		local fits = {}
		for _, len in ipairs(S.LENGTHS) do
			local chosen, mounts, used = S.Fit(len.minutes)
			fits[len.minutes] = { chosen = chosen, mounts = mounts, used = used }
		end
		local function fitFor(minutes)
			local f = fits[minutes]
			if not f then
				local chosen, mounts, used = S.Fit(minutes)
				f = { chosen = chosen, mounts = mounts, used = used }
				fits[minutes] = f
			end
			return f
		end

		local worst
		for _, len in ipairs(S.LENGTHS) do
			local f = fitFor(len.minutes)
			local chosen, used = f.chosen, f.used
			if used > len.minutes + 0.001 then
				worst = ("%d min session planned %.1f min of work"):format(len.minutes, used)
			end
			-- A longer session must never offer FEWER stops.
			if worst then break end
			if #chosen > 0 and used <= 0 then
				worst = ("%d min session planned stops costing nothing"):format(len.minutes)
			end
		end
		if worst then return false, worst end
		-- MOUNTS, not just stops. The first version of this check only
		-- compared stop counts, and the live diagnostic then showed
		-- "45 min -> 0.01 mounts" against "20 min -> 0.05" while the counts
		-- stayed flat at 1 and 1. Counting the wrong thing let a broken
		-- feature report PASS.
		local prevStops, prevMounts = -1, -1
		for _, len in ipairs(S.LENGTHS) do
			local f = fitFor(len.minutes)
			local chosen, mounts = f.chosen, f.mounts
			if #chosen < prevStops then
				return false, ("%d min offered fewer stops than a shorter session")
					:format(len.minutes)
			end
			if mounts < prevMounts - 1e-9 then
				return false, ("%d min expects %.2f mounts, fewer than a shorter "
					.. "session's %.2f"):format(len.minutes, mounts, prevMounts)
			end
			prevStops, prevMounts = #chosen, mounts
		end
		local twenty = fitFor(20)
		local longest = fitFor(S.LENGTHS[#S.LENGTHS].minutes)
		return true, ("%d lengths, all inside their clock; 20 min -> %d stops "
			.. "(~%.2f mounts), %d min -> %d stops (~%.2f mounts)"):format(
			#S.LENGTHS, #twenty.chosen, twenty.mounts,
			S.LENGTHS[#S.LENGTHS].minutes, #longest.chosen, longest.mounts)
	end)

	check("The contribution file is a template, not a guess", function()
		-- The export must round-trip through its own import as a NO-OP: every
		-- placeholder line has to be ignored. If an untouched template wrote
		-- zeroes into the database, contributing would corrupt the data it was
		-- meant to improve.
		local CO = MM.Contribute
		if not (CO and CO.Export and CO.Import) then return false, "contribute missing" end
		local saved = MM.db.contributions
		MM.db.contributions = {}
		local text, total = CO.Export()
		local applied, _, problems = CO.Import(text)
		local wrote = 0
		for _ in pairs(MM.db.contributions) do wrote = wrote + 1 end
		if applied > 0 or wrote > 0 then
			MM.db.contributions = saved
			pcall(CO.Apply)
			return false, ("its own export wrote %d records back — placeholders "
				.. "are being treated as data"):format(wrote)
		end
		-- IGNORED AND REJECTED ARE BOTH SILENT, AND ONLY ONE IS CORRECT.
		--
		-- The soloability placeholder used to read `solo = true   -- or false`,
		-- which parsed as the whole string INCLUDING the hint and was refused.
		-- The template round-tripped as a no-op the entire time -- by erroring
		-- on every line. Anyone who filled the file in got one complaint per
		-- answer. A no-op is only proof of a template if nothing complained.
		if #problems > 0 then
			MM.db.contributions = saved
			pcall(CO.Apply)
			return false, ("its own export raised %d complaint(s), e.g. %s — the "
				.. "template is not importable"):format(#problems, problems[1])
		end

		-- A NO-OP PROVES NOTHING ON ITS OWN.
		--
		-- This test passed for the entire life of the feature while the import
		-- was incapable of applying anything: the export writes the display
		-- name and the import looked it up in a table keyed by the LOWERCASED
		-- name, so every line resolved to "unknown mount". An unrecognised
		-- name is a no-op and an untouched placeholder is a no-op, and the
		-- test could not tell them apart.
		--
		-- So it now fills one line in and checks the value actually lands.
		-- THE FILL-IN HALF MUST NOT NEED A GAP TO EXIST.
		--
		-- It used to pick a name out of the export, which meant the half of the
		-- check that actually matters -- does a real answer land -- was skipped
		-- on any client with nothing left to contribute. Answering every open
		-- question therefore turned the check off, which is precisely backwards:
		-- a finished database is when you most want to know the importer still
		-- works, because that is when nobody is exercising it by hand.
		--
		-- The import does not care whether a mount has a gap. Any record it can
		-- name will do, so it takes one from the database itself.
		-- THE FIRST `| dropRate` IN THE EXPORT IS THE FORMAT COMMENT.
		--
		-- This scanned the export text for a name followed by `| dropRate`, and
		-- the export's own header line is `-- Format:  <Mount Name> | dropRate`.
		-- So the target was the literal string "-- Format:  <Mount Name>", the
		-- importer correctly refused to recognise it as a mount, and the check
		-- reported the importer for doing the right thing.
		--
		-- The comment above already says where the target should come from: the
		-- database. The import does not care whether a mount has a gap, so any
		-- record it can name will do -- and taking it from the database means
		-- this half cannot be switched off by answering every open question.
		local target, field, value, want
		for _, rec in ipairs(MM.DBList or {}) do
			if rec.name and not rec.stub and MM.DBByName[rec.name:lower()] then
				target, field, value, want = rec.name, "solo", "solo = false", false
				break
			end
		end
		if not target then
			MM.db.contributions = saved
			pcall(CO.Apply)
			return nil, "no records at all to test the importer with"
		end
		MM.db.contributions = {}
		local ok = CO.Import(("%s | %s"):format(target, value))
		local stored = MM.db.contributions[target] and MM.db.contributions[target][field]
		MM.db.contributions = saved
		pcall(CO.Apply)

		if ok < 1 or stored ~= want then
			return false, ("a filled %s line for %q applied %d and stored %s — the "
				.. "import cannot recognise its own export"):format(
				field, target, ok, tostring(stored))
		end
		return true, ("%d gaps exported; placeholders no-op without complaint, and "
			.. "a filled %s line lands on %s"):format(total, field, target)
	end)

	check("Reagents are priced by how you get them", function()
		-- A stack of herbs is not a stack of soulbound raid drops. The cost
		-- comes from the item's own class and binding -- live client data --
		-- rather than one flat rate per reagent.
		local CR = MM.Crafting
		if not (CR and CR.ReagentCost) then return false, "ReagentCost missing" end
		-- Linen Cloth: tradeable trade good. Should be cheap.
		local cheap = CR.ReagentCost(2589)
		-- Hearthstone: soulbound, and not a trade good.
		local bound = CR.ReagentCost(6948)
		if type(cheap) ~= "number" or type(bound) ~= "number" then
			return false, "ReagentCost did not return minutes"
		end
		if bound <= cheap then
			return nil, ("item data not cached yet (%.1f vs %.1f) — costs converge "
				.. "on the unknown estimate until the client loads them")
				:format(cheap, bound)
		end
		return true, ("buyable %.1f min/unit vs soulbound %.1f min/unit")
			:format(cheap, bound)
	end)

	check("Every gated mount names a character", function()
		-- Requirement — We should always recommend the best character to finish/get
		-- the mount. The old pair of helpers stayed SILENT when the current
		-- character was the right answer, so "you are already on the best
		-- character" and "this addon never considered it" looked identical.
		--
		-- Hard gates come first: recommending a Horde character for an
		-- Alliance-only mount is worse than saying nothing at all.
		local A = MM.Alts
		if not (A and A.Recommend) then return false, "Alts.Recommend missing" end
		if not (MM.db.alts and next(MM.db.alts)) then
			return nil, "no characters snapshotted yet"
		end
		local answered, silent, wrongFaction = 0, 0, {}
		for _, entry in ipairs(MM.Planner:GetPlan()) do
			local rec = entry.rec
			if rec and rec.conditions and #rec.conditions > 0 then
				local r = A.Recommend(rec)
				if not r then
					silent = silent + 1
				else
					answered = answered + 1
					if r.key and rec.faction then
						local snap = MM.db.alts[r.key]
						if snap and snap.faction and snap.faction ~= rec.faction then
							wrongFaction[#wrongFaction + 1] = rec.name
						end
					end
				end
			end
		end
		if #wrongFaction > 0 then
			return false, ("recommended a wrong-faction character for %d mounts: %s")
				:format(#wrongFaction, table.concat(wrongFaction, ", ", 1,
					math.min(#wrongFaction, 4)))
		end
		if answered == 0 then return nil, "no conditioned goals in the plan" end
		return true, ("%d gated goals answered, %d had no per-character angle")
			:format(answered, silent)
	end)

	check("Crafts cost their missing reagents", function()
		-- Requirement — Materials always need to be considered in all crafted
		-- mounts. A flat four-hour charge stopped crafts winning the list but
		-- said the same thing about three herbs and forty rare drops.
		--
		-- Reagents are HARVESTED from C_TradeSkillUI, never invented -- writing
		-- reagent lists from memory is precisely how two of fifteen secret
		-- chains came out wrong. So this asserts the wiring, and reports
		-- honestly when nothing has been harvested yet.
		local CR, P = MM.Crafting, MM.Planner
		if not (CR and CR.Progress and CR.IsCraft) then return false, "crafting missing" end
		local crafts, priced, mismatched = 0, 0, {}
		for _, rec in pairs(MM.DBByName) do
			if CR.IsCraft(rec) and rec.obtainable then
				crafts = crafts + 1
				local frac, mats = CR.Progress(rec)
				if frac then
					priced = priced + 1
					-- every reagent must report a coherent shortfall
					for _, m in ipairs(mats) do
						if m.short < 0 or m.short > m.count then
							mismatched[#mismatched + 1] = rec.name
							break
						end
					end
				end
			end
		end
		if #mismatched > 0 then
			return false, "incoherent reagent counts: " .. table.concat(mismatched, ", ", 1,
				math.min(#mismatched, 4))
		end
		if crafts == 0 then return nil, "no crafted mounts in the database" end
		if priced == 0 then
			return nil, ("%d crafts, none harvested yet — open a profession window")
				:format(crafts)
		end
		return true, ("%d crafts, %d with real reagent data"):format(crafts, priced)
	end)

	check("A snapshot never empties what it cannot read", function()
		-- The warband row went "prof:yes" to "prof:no" between two runs minutes
		-- apart on the same character. Nothing was unlearned -- the refresh
		-- cleared the table before reading, so one quiet login overwrote real
		-- data with nothing.
		local A = MM.Alts
		if not (A and A.Snapshot) then return nil, "no snapshot to test" end
		local db = MM.db and MM.db.alts
		if not db then return nil, "no warband data yet" end
		local before = 0
		for _, snap in pairs(db) do
			local p = snap.professions
			if p and next(p) then before = before + 1 end
		end
		pcall(A.Snapshot)
		local after = 0
		for _, snap in pairs(db) do
			local p = snap.professions
			if p and next(p) then after = after + 1 end
		end
		if after < before then
			return false, ("a refresh LOST professions: %d characters -> %d")
				:format(before, after)
		end
		return true, ("%d character(s) with professions, kept across a refresh")
			:format(after)
	end)

	check("A profession is a rank, not a yes", function()
		-- A craft mount can want Blacksmithing 300. "Has blacksmithing" cannot
		-- answer that, and the old scorer also gave half credit for ANY skill
		-- line -- so a character whose only trade was fishing could be
		-- suggested as the best alt for a blacksmithing mount.
		local A = MM.Alts
		if not (A and A.ScoreCondition) then return nil, "scorer not exposed" end
		local cond = { type = "PROFESSION", name = "Blacksmithing", amount = 300 }
		local skilled = { professions = { Blacksmithing = 300 } }
		local green   = { professions = { Blacksmithing = 75 } }
		local angler  = { professions = { Fishing = 300 }, skillLines = { [1] = 300 } }
		local a = A.ScoreCondition(skilled, cond)
		local b = A.ScoreCondition(green, cond)
		local c = A.ScoreCondition(angler, cond)
		if not (a > b) then return false, "300 must beat 75" end
		if c ~= 0 then
			return false, ("a different trade scored %.0f, must be 0"):format(c)
		end
		return true, ("skilled %.0f, part-way %.0f, wrong trade %.0f"):format(a, b, c)
	end)

	check("Warband counts, not just this character", function()
		-- Reagents in the warband bank are available to everyone, which is what
		-- makes "who should craft this" answerable at all. A count that only
		-- read the current character's bags would send the player farming
		-- materials they already own.
		local CR = MM.Crafting
		if not (CR and CR.Have) then return false, "crafting missing" end
		-- Hearthstone: every character has one, and it is never zero.
		local n = CR.Have(6948)
		if type(n) ~= "number" then return false, "Have() did not return a number" end
		local alts, withProf = 0, 0
		for _, snap in pairs(MM.db.alts or {}) do
			alts = alts + 1
			if snap.skillLines and next(snap.skillLines) then withProf = withProf + 1
			elseif snap.professions and next(snap.professions) then withProf = withProf + 1 end
		end
		if alts == 0 then return nil, "no characters snapshotted yet" end
		return true, ("%d characters known, %d with professions recorded")
			:format(alts, withProf)
	end)

	check("Nothing is both guaranteed and free", function()
		-- The generalisation of three separate bugs, all the same shape:
		-- a mount the planner believes is CERTAIN, costing nothing but the
		-- effort floor, because no part of its real price was modelled.
		--
		--   unmodelled achievements  Gorestrider Gronnling, 13 raid criteria,
		--                            scored as a guaranteed 45-minute mount
		--   unpriced vendor mounts   a currency cost that existed only in prose
		--   Protoform Synthesis      24 crafts with no reagents modelled, each
		--                            a guaranteed mount at the effort floor
		--
		-- Each was found by someone reading a list and saying "that is wrong".
		-- A guaranteed mount with no modelled cost has the highest density in
		-- the plan by construction, so it always wins -- which makes this the
		-- single most valuable invariant in the ranking.
		local P = MM.Planner
		if not (P and P.TimeCommitment and P.ExpectedMounts) then
			return false, "planner missing"
		end
		-- Scoped to categories whose cost IS a price -- materials, currency,
		-- reputation. Everywhere else the editorial `effort` rating is the
		-- designed fallback and charging the floor is correct; 413 records rely
		-- on it deliberately. Flagging those would bury the real finding in
		-- noise, which is how a failing check becomes one people stop reading.
		local PRICED = { CURRENCY = true, VENDOR = true, REP = true,
			TIMEWALKING = true, PROFESSION = true }
		local offenders, checked = {}, 0
		for _, entry in ipairs(P:GetPlan()) do
			local rec = entry.rec
			if rec and PRICED[rec.category] then
				checked = checked + 1
				local mounts = P.ExpectedMounts(entry) or 0
				local _, parts = P.TimeCommitment(entry)
				-- "nothing modelled" == the effort floor is the only line item
				local onlyFloor = parts and #parts == 1
					and tostring(parts[1].name):find("effort rating") ~= nil
				if mounts >= 0.9 and onlyFloor then
					offenders[#offenders + 1] = ("%s (%s)"):format(rec.name, rec.category)
				end
			end
		end
		if #offenders > 0 then
			table.sort(offenders)
			return false, ("%d goals are guaranteed AND free: %s%s"):format(
				#offenders, table.concat(offenders, ", ", 1, math.min(#offenders, 6)),
				#offenders > 6 and (" ...and %d more"):format(#offenders - 6) or "")
		end
		if checked == 0 then return nil, "no priced goals in the plan" end
		return true, ("%d priced goals, none guaranteed-and-free"):format(checked)
	end)

	check("A paragon mount is never a pickup", function()
		-- Requirement — It's still showing Paragon rewards when I do not have enough xp
		-- for paragon. The cause was a misread API -- IsFactionParagon means
		-- "paragon is UNLOCKED", not "a cache is waiting" -- so the condition
		-- reported MET and the record fell into the "just go buy it" branch.
		--
		-- A paragon mount is only ever a pickup when a finished cache is
		-- actually sitting unclaimed.
		local P, C = MM.Planner, MM.Conditions
		if not (C and C.ParagonProgress) then return false, "ParagonProgress missing" end
		local checked, bad = 0, {}
		for _, entry in ipairs(P:GetPlan()) do
			local rec = entry.rec
			for _, cond in ipairs((rec and rec.conditions) or {}) do
				if cond.type == "REP" and cond.standingName == "Paragon" then
					checked = checked + 1
					local _, _, pending = C.ParagonProgress(cond)
					local tier = P.CostParts and select(1, P.Handicap(entry)) or nil
					local met = C.EvaluateAll(rec)
					if met == true and not pending then
						bad[#bad + 1] = rec.name
					end
				end
			end
		end
		if #bad > 0 then
			return false, ("%d paragon goals claim their requirement is met "
				.. "with no cache pending: %s"):format(#bad, table.concat(bad, ", ", 1,
					math.min(#bad, 5)))
		end
		if checked == 0 then return nil, "no paragon goals in the plan" end
		return true, ("%d paragon goals, none misreported as ready"):format(checked)
	end)

	check("Onboarding writes the real settings", function()
		-- An onboarding flow with its own private copy of the settings is a bug
		-- generator: the player picks a theme in the welcome window, opens
		-- Options, and is told something different. Every control in the flow
		-- must write the SAME saved variable the Options panel reads.
		local O = MM.Onboarding
		if not O then return false, "onboarding module missing" end
		if type(O.SCHEMA) ~= "number" then
			return false, "onboarding must record a schema, not a boolean"
		end
		-- The keys the flow touches, and who else owns them.
		local owned = { "theme", "celebration", "celebrationShot", "celebrateAll" }
		local missing = {}
		for _, key in ipairs(owned) do
			-- theme is legitimately nil when automatic, so presence in the
			-- defaults table is what is being asserted, not truthiness
			if MM.db[key] == nil and key ~= "theme" then
				missing[#missing + 1] = key
			end
		end
		if #missing > 0 then
			return false, "settings the flow writes are absent from db: "
				.. table.concat(missing, ", ")
		end
		if MM.db.onboarded and MM.db.onboarded > O.SCHEMA then
			return false, ("saved schema %d is newer than this build's %d")
				:format(MM.db.onboarded, O.SCHEMA)
		end
		return true, ("schema %d, %d shared settings, no private copies")
			:format(O.SCHEMA, #owned)
	end)

	check("The plan summary keeps what the label drops", function()
		-- The header line was long enough to truncate, so the two facts a
		-- collector is deciding on -- how long, and do I end with a mount --
		-- were losing to stop and zone counts. The label carries those two and
		-- the tooltip carries everything, which only works if everything is
		-- actually there.
		local UI = MM.UI
		if not (UI and UI.SummaryLines) then return nil, "planner not built yet" end
		local lines = UI.SummaryLines()
		if not lines or #lines == 0 then return nil, "no plan to summarise" end
		local seen = {}
		for _, pair in ipairs(lines) do
			if type(pair[1]) ~= "string" or type(pair[2]) ~= "string" then
				return false, "a summary line is not a label/value pair"
			end
			if seen[pair[1]] then
				return false, "the summary says '" .. pair[1] .. "' twice"
			end
			seen[pair[1]] = true
		end
		if not (seen["Mounts on this plan"] and seen["Stops"]) then
			return false, "the counts dropped from the label are not in the tooltip"
		end
		return true, ("%d facts, none duplicated"):format(#lines)
	end)

	check("The window does not argue with itself", function()
		-- The missing list showed rows with "everything here is already on your
		-- plan" drawn over them, and the plan pane was blank while its header
		-- said 132 mounts. Both halves were right when written and stale when
		-- seen: hundreds of refreshes were running inside one another, each
		-- writing state computed at a different moment.
		local UI = MM.UI
		if not (UI and UI.RefreshPlanner and UI.RefreshPlannerNow) then
			return nil, "planner not built yet"
		end
		-- Asking many times in a row must not run many times in a row.
		local ran = 0
		local real = UI.RefreshPlannerNow
		UI.RefreshPlannerNow = function() ran = ran + 1 end
		for _ = 1, 25 do UI.RefreshPlanner() end
		UI.RefreshPlannerNow = real
		if ran > 1 then
			return false, ("25 requests ran the refresh %d times"):format(ran)
		end
		return true, ("25 requests collapsed to %d immediate pass%s"):format(
			ran, ran == 1 and "" or "es")
	end)

	check("Lists of choices are drawn as lists", function()
		-- The filters were buttons that opened radio menus, sitting beside a
		-- genuine dropdown for the session length -- the same gesture behind
		-- two different affordances, only one of which announced itself.
		local UI = MM.UI
		if not (UI and UI.MakePicker) then return false, "no picker builder" end
		local host = CreateFrame("Frame")
		local seen
		local pick = UI.MakePicker(host, "Type", { "A", "B" },
			{ A = "Alpha", B = "Beta" }, function(v) seen = v end, "B", "All", 140)
		if not pick then return false, "picker did not build" end
		if not pick.mmSetLabel then
			-- The cycler fallback is a legitimate outcome on older clients.
			return nil, "fell back to the cycler: no dropdown template here"
		end
		-- Building must NOT fire onChange -- the caller already applied the
		-- saved value, and re-firing it would rewrite the filters on open.
		if seen ~= nil then
			return false, "building the picker changed the setting to " .. tostring(seen)
		end
		return true, "dropdown built, initial value applied without firing"
	end)

	check("Every onboarding step draws", function()
		-- Steps are only reachable by clicking in-game, so a bad field
		-- reference inside one card could sit unnoticed until a new player --
		-- the one person who cannot work around it -- hit that screen.
		local O = MM.Onboarding
		if not (O and O.Show and O.GoTo and O.StepCount) then
			return false, "onboarding cannot be driven programmatically"
		end
		local wasOnboarded = MM.db.onboarded
		local ok, err = pcall(function()
			O.Show()
			for i = 1, O.StepCount() do
				if not O.GoTo(i) then error("could not reach step " .. i) end
			end
		end)
		local n = O.StepCount()
		if O.Hide then O.Hide() end
		MM.db.onboarded = wasOnboarded
		if not ok then return false, "a step failed to draw: " .. tostring(err) end
		if n < 4 then return false, ("only %d steps built"):format(n) end
		return true, ("%d steps, all drawn"):format(n)
	end)

	check("The window opens on the Planner", function()
		-- Requirement — we should also default to the planner tab with the main
		-- window. The Collection tab answers "what do we have"; the Planner
		-- answers "what should I do next", which is why the addon exists.
		local UI = MM.UI or MM.MainUI
		if not (UI and UI.SelectTab) then return nil, "main window not built yet" end
		-- IT USED TO SAY THIS RATHER THAN CHECK IT. The body confirmed SelectTab
		-- existed and then asserted where a fresh window lands, which it never
		-- looked at -- so moving the default would not have failed anything.
		local want = UI.DEFAULT_TAB
		if not want then return nil, "the default tab is not readable from here" end
		if want ~= UI.PLANNER_TAB then
			return false, ("a fresh window opens on tab %s, and the Planner is "
				.. "tab %s"):format(tostring(want), tostring(UI.PLANNER_TAB))
		end
		return true, ("a fresh window lands on tab %d, which is the Planner")
			:format(want)
	end)

	check("Odds and effort are neutral at their defaults", function()
		-- Two lenses were added to the objective because the matrix proved the
		-- odds and effort sliders reached nothing the router read. A new lens
		-- that shifts the SHIPPED ranking is a regression dressed as a feature,
		-- so the neutral point has to be exact rather than approximately right.
		local R = MM.Router
		if not (R.OddsLens and R.EffortLens) then return false, "lenses missing" end
		local saved = MM.db.weights
		MM.db.weights = { schema = 2 }
		MM:Fire("MM_WEIGHTS_CHANGED")
		local probe = { tier = 1, mounts = 0.25, workMinutes = 240 }
		local o, e = R.OddsLens(probe), R.EffortLens(probe)
		MM.db.weights = saved
		MM:Fire("MM_WEIGHTS_CHANGED")
		if o ~= 1 or e ~= 1 then
			return false, ("defaults are not neutral: odds %.6f, effort %.6f"):format(o, e)
		end
		return true, "both lenses are exactly 1.0 with default weights"
	end)

	check("Every slider survives its own extremes", function()
		-- Exponent lenses can produce NaN or infinity at the ends of a range,
		-- and a NaN in a sort comparator corrupts the whole route silently
		-- rather than erroring. Walk the corners.
		local R = MM.Router
		local saved = MM.db.weights
		local probes = {
			{ tier = 1, mounts = 0.01, workMinutes = 1 },
			{ tier = 1, mounts = 1, workMinutes = 1440 },
			{ tier = 1, mounts = 0, workMinutes = 0 },
			{ tier = 1, mounts = 4, workMinutes = 60, members = { {}, {}, {}, {} } },
		}
		local bad
		for _, odds in ipairs({ 0, 2500, 5000 }) do
			for _, effort in ipairs({ 0, 100, 500 }) do
				MM.db.weights = { schema = 2, odds = odds, effort = effort }
				MM:Fire("MM_WEIGHTS_CHANGED")
				for _, st in ipairs(probes) do
					local v = R.StopValue(st)
					if v ~= v or v == math.huge or v == -math.huge then
						bad = ("odds %d / effort %d -> %s"):format(odds, effort, tostring(v))
					end
				end
			end
		end
		MM.db.weights = saved
		MM:Fire("MM_WEIGHTS_CHANGED")
		if bad then return false, "produced a non-finite score: " .. bad end
		return true, "9 slider corners x 4 goal shapes, all finite"
	end)

	check("A portal hub is not a dead end", function()
		-- a player standing in Zuldazar with three Orgrimmar teleports in their bags and
		-- was told to fly to the Dazar'alor portal room and "take the portal to
		-- Orgrimmar, then the Dornogal portal". The engine had rejected all
		-- three with "lands on a different continent" -- while the instruction
		-- it printed proved Orgrimmar was exactly where it wanted them.
		--
		-- A landing is useful for everywhere it CONNECTS to, not only where it
		-- lands. This proves the old blanket rejection is gone.
		local TP = MM.Teleports
		if not (TP and TP.Evaluate) then return false, "teleport layer missing" end
		-- Router:Current() answers only once the player has STARTED a route,
		-- which is right for the arrow and wrong here: a built route with 106
		-- stops is not "nothing to price". Gating on it left this check and the
		-- one below permanently degraded, and the summary then described them as
		-- optional features this client lacks -- which they are not. They were
		-- features nobody had switched on.
		--
		-- Any stop with a world position exercises the same pricing path.
		local goal = MM.Router and MM.Router:Current()
		if not (goal and goal.world) then goal = firstPlacedStop() end
		if not (goal and goal.world) then return nil, "no placed stop to price" end
		TP.Evaluate(goal.continent, goal.world, math.huge)
		for _, r in ipairs(TP.rejections) do
			if r:find("lands on a different continent") then
				return false, "the blanket continent rejection is still live: " .. r
			end
		end
		local hubbed = 0
		for _, c in ipairs(TP.considered) do
			if c.via then hubbed = hubbed + 1 end
		end
		return true, ("%d options priced, %d of them through a portal hub")
			:format(#TP.considered, hubbed)
	end)

	check("Every priced option shows its arithmetic", function()
		-- The ledger is written BY the decision, not reconstructed after it.
		-- If the parts do not sum to the cost that won, the diagnostic is
		-- describing a route we did not take.
		local TP = MM.Teleports
		if not (TP and TP.considered) then return false, "no ledger" end
		-- Price something first rather than reporting an empty ledger as
		-- "nothing to check". The ledger is a side effect of Evaluate, so a
		-- check that only reads it can never run on its own.
		if #TP.considered == 0 then
			local goal = firstPlacedStop()
			if goal and TP.Evaluate then TP.Evaluate(goal.continent, goal.world, math.huge) end
		end
		if #TP.considered == 0 then return nil, "no placed stop to price" end
		for _, c in ipairs(TP.considered) do
			local sum = c.flight + c.hub + c.wait * 25
			if math.abs(sum - c.cost) > 1 then
				return false, ("%s itemises %.0f but cost %.0f"):format(c.name, sum, c.cost)
			end
		end
		return true, ("%d options itemise and sum exactly"):format(#TP.considered)
	end)

	check("Free travel really frees the route", function()
		-- The matrix caught this: "Distance is free — travel 0" produced a
		-- byte-identical route to the defaults. The greedy chain honoured the
		-- slider and then 2-opt reordered by raw yards and undid it.
		local R = MM.Router
		if not (R and R.TwoOpt and R.TravelScale) then return false, "router missing" end
		local saved = MM.db.weights
		local restore = function() MM.db.weights = saved; MM:Fire("MM_WEIGHTS_CHANGED") end
		local list = {}
		for i = 1, 6 do
			list[i] = { world = { x = (i % 2 == 0) and 90000 or 0, y = i * 1000 } }
		end
		MM.db.weights = { schema = 2, travel = 0 }
		MM:Fire("MM_WEIGHTS_CHANGED")
		local ok = (R.TravelScale() == 0)
		local out = R.TwoOpt(list, { x = 0, y = 0 })
		local untouched = true
		for i = 1, #list do
			if out[i] ~= list[i] then untouched = false break end
		end
		restore()
		if not ok then return false, "travel 0 did not reach the router" end
		if not untouched then
			return false, "2-opt still reordered a route the player asked not to optimise"
		end
		return true, "2-opt and block reordering stand down at travel 0"
	end)

	check("Tracked secret steps really track", function()
		-- Wowhead's secret guides publish the community's own progress-macro
		-- quest ids. Where a record carries them, StepProgress must report a real
		-- step number instead of "none of them trackable" -- that is the whole
		-- difference between a checklist and a wall of text.
		local A = MM.Acquire
		if not (A and A.StepProgress) then return false, "StepProgress missing" end
		local tracked = 0
		for _, rec in pairs(MM.DBByName) do
			local steps = rec.acquire and rec.acquire.steps
			if steps then
				local withQuest = 0
				for _, step in ipairs(steps) do
					if step.quest then withQuest = withQuest + 1 end
				end
				if withQuest > 0 then
					tracked = tracked + 1
					if withQuest ~= #steps then
						return false, ("%s tracks %d of %d steps — all or none, or the")
							:format(rec.name, withQuest, #steps)
							.. " step number it reports is a lie"
					end
					local text = A.StepProgress(rec)
					if not text or text:find("none of them trackable") then
						return false, rec.name .. " has quest ids but reports nothing tracked"
					end
				end
			end
		end
		if tracked == 0 then return nil, "no secret carries per-step quest ids yet" end
		return true, ("%d secrets report exact step progress"):format(tracked)
	end)

	check("Secret chains carry steps and a time cost", function()
		-- Requirement — I only care about the acquire block, difficulty, and time
		-- committment. A secret with none of those is an effort number and a
		-- sentence, which tells a player nothing about what they are signing up
		-- for.
		local P, A = MM.Planner, MM.Acquire
		local withSteps, missing = 0, {}
		for _, rec in pairs(MM.DBByName) do
			if rec.category == "PUZZLE" then
				if rec.acquire and rec.acquire.steps then
					withSteps = withSteps + 1
					if not rec.acquire.hours then
						missing[#missing + 1] = rec.name .. " (steps but no hours)"
					end
					for i, step in ipairs(rec.acquire.steps) do
						if type(step.text) ~= "string" or step.text == "" then
							return false, ("%s step %d has no text"):format(rec.name, i)
						end
					end
				end
			end
		end
		if #missing > 0 then return false, table.concat(missing, ", ") end
		if withSteps == 0 then return false, "no secret carries a step chain" end

		-- and the hours must actually reach the estimate
		for _, rec in pairs(MM.DBByName) do
			if rec.acquire and rec.acquire.hours then
				local entry = MM.Scanner.bySpell[rec.spellID]
				if entry then
					local minutes = P.TimeCommitment(entry)
					if minutes < rec.acquire.hours * 60 * 0.9 then
						return false, ("%s: %d hours declared, %.0f minutes costed")
							:format(rec.name, rec.acquire.hours, minutes)
					end
					break
				end
			end
		end
		return true, ("%d secrets carry a chain and an hours estimate"):format(withSteps)
	end)

	check("A raid meta is never field work", function()
		-- Bloodgorged Crawg is Glory of the Uldir Raider, a thirteen-criteria
		-- meta, and it led EVERY preset -- including the drops-only one --
		-- because its effort was recorded as 2 and the tier shortcut sent
		-- anything at effort <= 2 straight to the field tier. One optimistic
		-- number in the data reclassified a raid meta as an errand.
		local P = MM.Planner
		local meta = { rec = { category = "ACHIEVEMENT", effort = 2, timePerAttempt = 20,
			source = "Reward from the achievement Glory of the Uldir Raider" } }
		local errand = { rec = { category = "ACHIEVEMENT", effort = 2, timePerAttempt = 10,
			source = "Loot 5 treasures in the zone" } }
		local metaTier = P.Rank(meta)
		local errandTier = P.Rank(errand)
		if metaTier == P.TIER.FIELD then
			return false, "a Glory meta was classified as field work"
		end
		if errandTier ~= P.TIER.FIELD then
			return nil, "short achievements no longer read as field work either"
		end
		return true, "metas rank as projects, short objectives as field work"
	end)

	check("Non-soloable metas are flagged", function()
		-- Wowhead's Soloist's guide marks the metas that cannot be soloed due to
		-- game mechanics. Those carry solo = false; everything else is assumed
		-- soloable, by design: blanket-grouping punishes the many to guard
		-- against the few.
		local flagged, group = 0, 0
		for _, rec in pairs(MM.DBByName) do
			if rec.solo == false then
				flagged = flagged + 1
				if rec.category == "ACHIEVEMENT" then group = group + 1 end
			end
		end
		if flagged == 0 then return false, "no record carries solo = false" end
		return true, ("%d records flagged as needing a group, %d of them achievements")
			:format(flagged, group)
	end)

	check("The default rare alert actually plays", function()
		-- Six alert sounds once shared one fallback because the code guessed
		-- FileDataIDs for game audio. The murloc is a file WE ship, which plays
		-- by path -- a different call, and silent rather than erroring if the
		-- wrong one is used. This asserts the file is reachable and the default
		-- is the one we think it is.
		local RA = MM.RareAlert
		if not (RA and RA.SOUNDS and RA.SOUNDS[1]) then return false, "no alert sounds" end
		local first = RA.SOUNDS[1]
		if first.key ~= "murloc" then
			return false, ("default is %q, expected murloc"):format(tostring(first.key))
		end
		if not first.file then return false, "murloc has no file path" end
		-- Ask the client whether it will actually play. willPlay == false means
		-- the file is missing or the format is not supported, which is exactly
		-- the failure the old id-guessing produced and nobody could see.
		if not PlaySoundFile then return nil, "no PlaySoundFile on this client" end
		local ok, willPlay = pcall(PlaySoundFile, first.file, "Master")
		if not ok then return false, "PlaySoundFile threw on the shipped file" end
		if willPlay == false then
			return false, "the client refused to play " .. first.file
		end
		return true, "murloc leads the list and the client accepts the file"
	end)

	check("A volume somebody chose is not raised to maximum", function()
		-- Every audio CVar written costs a stall, and the alert fires the frame
		-- a vignette appears -- reported from play as the game hitching as the
		-- alert pops. Master volume was forced to 1 whenever it was not already
		-- 1, so anyone playing at any other level paid a write on every alert
		-- and another three seconds later.
		--
		-- It is also not what the option is for. Making an alert audible means
		-- not letting it be SILENT; it does not mean playing it at full volume
		-- over a level somebody set deliberately.
		local RA = MM.RareAlert
		if not (RA and RA.ForceAudible and RA.RestoreSound) then
			return false, "force/restore not present"
		end
		if not (GetCVar and SetCVar) then return nil, "no CVar API on this client" end
		local was = GetCVar("Sound_MasterVolume")
		local pending = MM.db.rareAlertCVarRestore
		MM.db.rareAlertCVarRestore = nil

		-- Quiet, but plainly audible: a level a person picked.
		pcall(SetCVar, "Sound_MasterVolume", "0.6")
		local staged = tonumber(GetCVar("Sound_MasterVolume"))
		RA.ForceAudible()
		local after = tonumber(GetCVar("Sound_MasterVolume"))
		local touched = MM.db.rareAlertCVarRestore
			and MM.db.rareAlertCVarRestore.Sound_MasterVolume ~= nil

		RA.RestoreSound()
		pcall(SetCVar, "Sound_MasterVolume", was)
		MM.db.rareAlertCVarRestore = pending

		if staged and math.abs(staged - 0.6) > 0.01 then
			return nil, "this client would not accept 0.6 as a volume"
		end
		if touched then
			return false, "an audible volume was saved for restore, so it was forced"
		end
		if after and math.abs(after - 0.6) > 0.01 then
			return false, ("0.6 was changed to %s"):format(tostring(after))
		end
		return true, "0.6 left alone; only a muted client is raised"
	end)

	check("Forcing the alert audible restores every setting it changed", function()
		-- This one is worth a test because the failure is invisible and lasting:
		-- a master volume left at 1.0 does not announce itself, and the player
		-- has no reason to suspect a mount addon. Drive the whole cycle here --
		-- read, force, restore -- and prove the numbers come back.
		local RA = MM.RareAlert
		if not (RA and RA.ForceAudible and RA.RestoreSound) then
			return false, "force/restore not present"
		end
		if not (GetCVar and SetCVar) then return nil, "no CVar API on this client" end
		local watched = { "Sound_EnableAllSound", "Sound_MasterVolume",
			"Sound_EnableSoundWhenGameIsInBG" }
		local before = {}
		for _, cv in ipairs(watched) do before[cv] = GetCVar(cv) end

		-- Start from a known-hostile state: everything a muted player would have.
		pcall(SetCVar, "Sound_MasterVolume", "0")
		local staged = GetCVar("Sound_MasterVolume")

		local pending = MM.db.rareAlertCVarRestore
		MM.db.rareAlertCVarRestore = nil
		RA.ForceAudible()
		local forced = GetCVar("Sound_MasterVolume")
		local saved = MM.db.rareAlertCVarRestore
		local persisted = saved and saved.Sound_MasterVolume
		RA.RestoreSound()
		local after = GetCVar("Sound_MasterVolume")

		-- Whatever the outcome, hand the player back what they came in with.
		for _, cv in ipairs(watched) do
			if before[cv] ~= nil then pcall(SetCVar, cv, before[cv]) end
		end
		MM.db.rareAlertCVarRestore = pending

		if tonumber(forced) ~= 1 then
			return false, ("master volume was %s during the alert, expected 1")
				:format(tostring(forced))
		end
		-- The saved copy lives in SavedVariables so a crash mid-alert can still
		-- be undone at the next login. A local would not survive that.
		if persisted ~= staged then
			return false, ("saved %q for restore, had %q")
				:format(tostring(persisted), tostring(staged))
		end
		if after ~= staged then
			return false, ("restored to %s, expected %s"):format(tostring(after), tostring(staged))
		end
		if MM.db.rareAlertCVarRestore ~= pending then
			return false, "restore left its own bookkeeping behind"
		end
		return true, "raised to 1, put back to " .. tostring(staged)
	end)

	check("Presets are coherent and round-trip", function()
		-- A preset that does not restore exactly, or that no longer matches
		-- itself after being applied, silently strands the player on settings
		-- they cannot get back to.
		local W = MM.Weights
		if not W.PRESETS or #W.PRESETS == 0 then return false, "no presets defined" end
		local saved = MM.db.weights
		local bad, onDefaults
		-- SEVEN RE-PLANS FOR STATES NOBODY SEES. Applying a preset announces
		-- the change, and announcing it re-plans; four presets plus the
		-- defaults probe did that seven times inside one check and threw
		-- "script ran too long" on slower hardware. The real ApplyPreset still
		-- runs -- testing a copy of it would test the copy -- it simply is not
		-- allowed to tell the plan about states that exist for one line.
		if not W.Silent then return false, "W.Silent missing" end
		W.Silent(function()
			for _, preset in ipairs(W.PRESETS) do
				W.ApplyPreset(preset.key)
				local current = W.CurrentPreset()
				if not (current and current.key == preset.key) then
					bad = preset.name .. " does not match itself once applied"
					return
				end
				if not (preset.blurb and preset.expect) then
					bad = preset.name .. " has no description of what it should do"
					return
				end
			end
			-- Balanced must BE the defaults, not an approximation of them.
			MM.db.weights = nil
			onDefaults = W.CurrentPreset()
		end)
		MM.db.weights = saved
		-- Once, now that the settings are back where the player left them.
		W.Changed()
		if bad then return false, bad end
		if not (onDefaults and onDefaults.key == "balanced") then
			return false, "the shipped defaults are not the Balanced preset"
		end
		return true, ("%d presets, defaults are Balanced"):format(#W.PRESETS)
	end)

	check("Absolute priority really is absolute", function()
		-- "Legacy Dungeons & Raids ... the first things on the list should
		-- ALWAYS be those." A multiplier cannot deliver that -- a vendor mount is
		-- a guaranteed mount in ten minutes against a 1% raid drop in twenty, a
		-- two-hundred-fold gap in value density. At the top of the range the
		-- ordering goes lexicographic instead.
		local R, W = MM.Router, MM.Weights
		local saved = MM.db.weights
		MM.db.weights = { schema = 2, priority = 1.5 }
		MM:Fire("MM_WEIGHTS_CHANGED")
		local rich = { tier = MM.Planner.TIER.GROUP, mounts = 1, workMinutes = 10,
			handicap = 0, world = { x = 0, y = 0 } }
		local poor = { tier = MM.Planner.TIER.PICKUP, mounts = 0.01, workMinutes = 20,
			handicap = 0, world = { x = 0, y = 0 } }
		local top = R.SelectionScore(poor, 0, poor.workMinutes)
		local bottom = R.SelectionScore(rich, 0, rich.workMinutes)
		MM.db.weights = saved
		MM:Fire("MM_WEIGHTS_CHANGED")
		if top <= bottom then
			return false, "a bottom-priority goal outscored a top-priority one at max strength"
		end
		return true, "rank decides outright at maximum strength"
	end)

	check("A sitting's worth of work leads", function()
		-- the player: an 8-hour guaranteed grind one minute away must not beat a
		-- 30-minute dungeon at 1% twenty minutes away. By raw density the grind
		-- wins six to one; by what you can actually finish tonight it does not,
		-- and a route is a list of things you DO.
		local R = MM.Router
		local here = { continent = 1, world = { x = 0, y = 0 } }
		local grind = { continent = 1, world = { x = 1500, y = 0 },
			mounts = 1, workMinutes = 480, tier = MM.Planner.TIER.PICKUP, handicap = 0 }
		local dungeon = { continent = 1, world = { x = 30000, y = 0 },
			mounts = 0.01, workMinutes = 10, tier = MM.Planner.TIER.PICKUP, handicap = 0 }
		local chain = R.NearestChain({ grind, dungeon }, here)
		if chain[1] ~= dungeon then
			return false, "an 8-hour grind led over a closed 30-minute action"
		end
		-- and the exception the named exception: with nothing shorter available, the
		-- grind IS the best thing left, with no special case for it
		local alone = R.NearestChain({ grind }, here)
		if alone[1] ~= grind then return false, "the grind vanished when alone" end
		return true, "short closed work leads; long grinds surface when it is gone"
	end)

	check("Detours are never worthless", function()
		-- Bloodthirsty Dreadwing -- a mount the player cannot afford, expected value
		-- zero -- was slotted in at position ONE for being a minute off the
		-- path. Free is not the same as worth doing, and the lead position is
		-- the one place that must never be filler.
		local R = MM.Router
		if #R.route == 0 then return nil, "no route built" end
		if R.route[1].opportunistic then
			return false, "the route opens with a detour rather than a real goal"
		end
		local bad = {}
		for i, stop in ipairs(R.route) do
			if stop.opportunistic and (stop.mounts or 0) <= 0.0001 then
				bad[#bad + 1] = ("%s (#%d)"):format(stop.label or "?", i)
				if #bad >= 3 then break end
			end
		end
		if #bad > 0 then
			return false, "woven in despite being worth nothing: " .. table.concat(bad, ", ")
		end
		return true, "every detour is worth taking"
	end)

	check("Unfinishable goals are parked, not routed", function()
		-- the player: a mount costing 1,000 medals they have none of was FIRST, then
		-- SECOND. Pushing it down was treating the symptom -- a goal that yields
		-- nothing when you arrive is not a stop at all, and the fix is to park
		-- it with the lockout-blocked goals where it is still visible.
		local R, P = MM.Router, MM.Planner
		if #R.route == 0 then return nil, "no route built" end
		local offenders = {}
		for i, stop in ipairs(R.route) do
			for _, m in ipairs(stop.members or { stop }) do
				if m.entry and P.CompletableNow(m.entry) == false
					and P.ExpectedMounts(m.entry) <= 0 then
					offenders[#offenders + 1] = ("%s (#%d)"):format(m.entry.name, i)
					if #offenders >= 3 then break end
				end
			end
			if #offenders >= 3 then break end
		end
		if #offenders > 0 then
			return false, "routed despite yielding nothing: " .. table.concat(offenders, ", ")
		end
		return true, ("%d goals parked as unreachable for now"):format(#R.deferred)
	end)

	check("Prerequisites add up, they do not compete", function()
		-- the player: a unique currency to farm, criteria to finish, a reputation to
		-- grind -- "these all factor into the total time equation". They are
		-- SEQUENTIAL, so they add. Taking the worst would price a mount needing
		-- exalted AND a thousand medals the same as one needing only the medals.
		local P = MM.Planner
		-- Two DIFFERENT currencies: two separate grinds. Using a rep condition
		-- here failed for a boring reason -- an invented faction name yields no
		-- progress at all, so there was nothing to add -- and a test that fails
		-- because its fixture is unreadable tells you nothing about the code.
		-- AMOUNTS NO CHARACTER COULD ALREADY HOLD.
		--
		-- This asked for 5,000 of each and failed on a character who happened to
		-- be sitting on 5,000 War Resources: that prerequisite then cost nothing,
		-- the two-condition case equalled the one-condition case, and the check
		-- reported a costing bug that did not exist. It passed on the character
		-- it was written against, which is the whole trouble with a fixture that
		-- reads live state.
		--
		-- The cost model SHOULD subtract what you already have -- that is the
		-- point of it. So the fixture asks for more than anyone can have, and
		-- the check goes back to testing the one thing it is about: that two
		-- prerequisites add up rather than compete.
		local both = { rec = { category = "CURRENCY", timePerAttempt = 10, effort = 3,
			conditions = {
				{ type = "CURRENCY", id = 1166, name = "Timewarped Badge", amount = 5000000 },
				{ type = "CURRENCY", id = 1560, name = "War Resources", amount = 5000000 },
			} } }
		local justCurrency = { rec = { category = "CURRENCY", timePerAttempt = 10, effort = 3,
			conditions = {
				{ type = "CURRENCY", id = 1166, name = "Timewarped Badge", amount = 5000000 },
			} } }
		local a = P.TimeCommitment(both)
		local b = P.TimeCommitment(justCurrency)
		if a <= b then
			return false, ("two prerequisites cost %.0f min, one costs %.0f"):format(a, b)
		end
		return true, ("two prerequisites %.0f min vs one %.0f"):format(a, b)
	end)

	check("Time estimates itemise themselves", function()
		-- An unexplained "about 14 hours" invites exactly one question, so the
		-- breakdown has to exist and has to sum to the figure shown.
		local P = MM.Planner
		local checked = 0
		for _, entry in ipairs(MM.Scanner.mounts) do
			if not entry.collected and entry.rec then
				local total, parts = P.TimeCommitment(entry)
				if #parts == 0 then return false, entry.name .. " gave no breakdown" end
				local sum = 0
				for _, part in ipairs(parts) do sum = sum + part.minutes end
				if math.abs(sum - total) > 0.01 then
					return false, ("%s: parts %.1f, total %.1f"):format(entry.name, sum, total)
				end
				checked = checked + 1
				if checked >= 40 then break end
			end
		end
		if checked == 0 then return nil, "nothing uncollected to check" end
		return true, ("%d estimates itemise and sum exactly"):format(checked)
	end)

	check("A price we cannot read is not free", function()
		-- The exact shape of the Bloodthirsty Dreadwing bug: category CURRENCY,
		-- no conditions block, so the cost exists only in prose. Charging it as
		-- a fifteen-minute errand put it at the top of the list.
		local P = MM.Planner
		local priced = { rec = { category = "CURRENCY", timePerAttempt = 10, effort = 3,
			source = "Sold for 750 Honorbound Service Medals",
			conditions = { { type = "CURRENCY", id = 1, name = "x", amount = 750 } } } }
		local unpriced = { rec = { category = "CURRENCY", timePerAttempt = 10, effort = 3,
			source = "Sold for 750 Honorbound Service Medals" } }
		local quick = { rec = { category = "DROP", timePerAttempt = 10, effort = 1,
			dropRate = 100 } }
		local a, b = P.EffortMinutes(unpriced), P.EffortMinutes(quick)
		if a <= b then
			return false, ("unpriced purchase costs %.0f min, a quick drop %.0f"):format(a, b)
		end
		if P.CompletableNow(unpriced) ~= nil then
			return false, "an unreadable price reported a definite answer"
		end
		return true, ("unknown price costs %.0f min against %.0f for a quick job"):format(a, b)
	end)

	check("A teleport switched off is never suggested", function()
		-- Asked for directly: "I don't want it to suggest my M+ dungeon
		-- teleports." A switch is only worth having if it reaches the ROUTE, not
		-- merely the list -- so this turns a real option off, reads back every
		-- place the router asks about teleports, and puts it back.
		local TP = MM.Teleports
		if not (TP and TP.Switchable and TP.SetOff) then return nil, "travel layer not loaded" end
		local list = TP.Switchable()
		if #list == 0 then return nil, "this character can press no teleport" end
		local victim
		for _, item in ipairs(list) do if not item.off then victim = item break end end
		if not victim then return true, ("%d option(s), all switched off already")
			:format(#list) end

		local before = #TP.Options()
		TP.SetOff(victim.key, true)
		local during = #TP.Options()
		local stillListed = false
		for _, g in ipairs(TP.Gates() or {}) do
			if g.name == victim.name and g.usable then stillListed = true end
		end
		TP.SetOff(victim.key, false)
		local after = #TP.Options()

		if during >= before then
			return false, ("%q was switched off and the router still offered %d option(s)")
				:format(victim.name, during)
		end
		if stillListed then
			return false, ("%q was switched off and still reads as usable"):format(victim.name)
		end
		if after ~= before then
			return false, ("switching %q back on left %d option(s), not the %d it started with")
				:format(victim.name, after, before)
		end
		return true, ("%d switchable option(s); turning %q off removed it from the "
			.. "route and turning it back on restored it"):format(#list, victim.name)
	end)

	check("Travel options respect their gates", function()
		-- Class, faction and profession are independent gates. Any class can be
		-- an Engineer, so the wormholes must never be filtered by class -- and
		-- nothing offered may be something this character cannot press.
		local TP = MM.Teleports
		if not (TP and TP.Landings) then return nil, "teleport layer absent" end
		local _, class = UnitClass("player")
		local faction = UnitFactionGroup("player")
		local landings = TP.Landings({})
		for _, l in ipairs(landings) do
			if not l.world then return false, l.name .. " has no destination" end
		end
		-- a Horde character must never be offered a Stormwind teleport
		local wrongCapital = (faction == "Horde") and "Stormwind City"
			or (faction == "Alliance") and "Orgrimmar" or nil
		if wrongCapital then
			for _, l in ipairs(landings) do
				if l.place == wrongCapital then
					return false, ("%s offers %s to a %s character")
						:format(l.name, wrongCapital, faction)
				end
			end
		end
		return true, ("%d usable for a %s %s"):format(#landings, faction or "?",
			class and class:lower() or "?")
	end)

	check("Every layer records its ordering", function()
		-- Preference, then grouping, then the clock. If a layer stops recording
		-- where it put things, "why is this here" goes back to being a guess.
		local R = MM.Router
		if #R.route == 0 then return nil, "no route built yet" end
		local missing = 0
		for _, stop in ipairs(R.route) do
			if not stop.layerRouted then missing = missing + 1 end
			for _, m in ipairs(stop.members or { stop }) do
				if not m.layerPreference then missing = missing + 1 end
			end
		end
		if missing > 0 then
			return false, ("%d stops carry no layer history"):format(missing)
		end
		return true, ("%d stops record all three layers"):format(#R.route)
	end)

	check("A teleport beats a long flight", function()
		-- the rule: a ten-minute flight on your own continent should lose to
		-- a wormhole plus two minutes. Nothing about distance can express that,
		-- so the test is on the travel-time function itself.
		local TP = MM.Teleports
		if not (TP and TP.TravelMinutes) then return nil, "teleport layer absent" end
		local landings = TP.Landings({})
		if #landings == 0 then
			return nil, "no teleports usable on this character to compare against"
		end
		-- somewhere far on the continent we can reach by teleport
		local target = landings[1]
		local near = { x = target.world.x + 100, y = target.world.y }
		local far = { x = target.world.x + 900000, y = target.world.y }
		local viaPort = TP.TravelMinutes(target.continent, far, target.continent, near, {})
		local flying = TP.TravelMinutes(target.continent, far, target.continent, far, {})
		if not (viaPort and flying) then return false, "travel time returned nothing" end
		if viaPort >= 900000 / (25 * 60) then
			return false, ("a %.0f-minute flight was not beaten by a teleport"):format(viaPort)
		end
		return true, ("%.1f min via %s instead of a long flight"):format(viaPort, target.name)
	end)

	check("The route's two clocks agree about which is bigger", function()
		-- The route reports travel-and-visits alongside the whole job, and the
		-- per-stop figures count against the SECOND. Nothing enforced the
		-- relationship, so a stop could report a time past the end of the
		-- route and both numbers still be individually correct.
		local R = MM.Router
		local t = R.totals
		if not t or (t.stops or 0) == 0 then return nil, "no route built yet" end
		local travel, whole = t.routeMinutes or 0, t.minutes or 0
		if travel > whole + 0.01 then
			return false, ("travelling and visiting (%.0f min) exceeds the whole "
				.. "job (%.0f min)"):format(travel, whole)
		end
		local last = R.route[#R.route]
		local final = last and last.cumulativeMinutes
		if not final then return nil, "stops carry no cumulative time" end
		if final > whole + 0.01 then
			return false, ("the last stop lands at %.0f min on a plan totalling "
				.. "%.0f"):format(final, whole)
		end
		return true, ("%.0f min travelling and visiting inside %.0f min of work; "
			.. "last stop lands at %.0f"):format(travel, whole, final)
	end)

	check("A reward tier is read from the tooltip, never invented", function()
		-- The reward line a player sees is NOT in the POI's description --
		-- checked against the client's own generated documentation, where
		-- AreaPOIInfo has a tooltipWidgetSet and no reward field at all. So the
		-- tier can only come from the widgets, and a gate that cannot read them
		-- must say "cannot tell" rather than "already taken".
		local A = MM.Assaults
		if not (A and A.WidgetText) then return false, "WidgetText missing" end
		-- A set nobody has must yield nothing, not an empty-string "answer".
		if A.WidgetText(-1) ~= nil then
			return false, "a widget set that does not exist returned text"
		end
		if A.WidgetText(nil) ~= nil then
			return false, "no widget set at all returned text"
		end
		local reading, blind = 0, 0
		for _, gate in pairs(A.rotatingGates or {}) do
			if gate.firstReward then
				local live = A.FindRotating and A.FindRotating(gate)
				if live then
					if live.widgetSet then reading = reading + 1 else blind = blind + 1 end
				end
			end
		end
		return true, ("no widgets, no verdict; %d live banner(s) carry a tooltip "
			.. "set, %d do not"):format(reading, blind)
	end)

	check("A missing banner is never read as a completion", function()
		-- The reward tier on a live Grand Hunt banner says whether the first
		-- run of the week is spent, and that is a real answer. A MISSING banner
		-- is not: the hunt rotates between four zones, so absence means it is
		-- running elsewhere, or the zone is not loaded, or the map is filtered.
		--
		-- This exact confusion already hid this exact goal once, by treating a
		-- lookup that failed as a lookup that answered. So the rule is asserted
		-- rather than described: no banner must yield nil, never false.
		local A = MM.Assaults
		if not (A and A.FirstRewardAvailable) then
			return false, "FirstRewardAvailable missing"
		end
		local nowhere = { key = "nowhere", label = "Nothing",
			maps = { -1 }, match = { "nothing that exists" },
			firstReward = { "a bag nobody has" } }
		local verdict = A.FirstRewardAvailable(nowhere)
		if verdict ~= nil then
			return false, ("a gate with no banner anywhere returned %s — absence "
				.. "is being read as an answer"):format(tostring(verdict))
		end
		-- and a gate that declares no reward needle must also refuse
		local noNeedle = { key = "nowhere", maps = { -1 }, match = { "x" } }
		if A.FirstRewardAvailable(noNeedle) ~= nil then
			return false, "a gate with no reward needle still returned a verdict"
		end
		local gates, watched = 0, 0
		for _, g in pairs(A.rotatingGates or {}) do
			gates = gates + 1
			if g.firstReward then watched = watched + 1 end
		end
		return true, ("no banner yields no verdict; %d rotating gate(s), %d read "
			.. "their reward tier"):format(gates, watched)
	end)

	check("No single check runs long enough to be killed", function()
		-- A check that outlasts the client's patience throws "script ran too
		-- long" at whatever line it happened to be on, which is never the line
		-- that matters -- the last one blamed the event dispatcher while the
		-- real cost was seven re-plans it did not need. Slicing the suite
		-- cannot help: a slice is only allowed to stop BETWEEN checks.
		local worst, worstMs, n, builds = T.SlowestCheck()
		if not worst and not worstMs then
			return nil, "nothing timed yet this session"
		end
		-- THE LINE IS CALIBRATED, not chosen.
		--
		-- 500 ms was a guess, and it kept failing the router model -- a check
		-- that demonstrably SURVIVES on the slowest machine here, at roughly
		-- twice this cost. The one that was actually killed was doing seven
		-- re-plans. So the evidence puts the client's real limit above the
		-- model and below that, and shaving the model further was chasing a
		-- number rather than a fault: two rounds of genuine optimisation moved
		-- it 1,184 ms to 1,033.
		--
		-- 1,200 ms sits above what is known to survive and well below what is
		-- known to die. It is a REGRESSION TRIPWIRE -- something new getting
		-- slow -- and not a proof of safety, which nothing here can offer
		-- without a machine that fails.
		if worstMs > 1200 then
			return false, ("%s takes %d ms in one uninterrupted run — past what "
				.. "is known to survive on slower hardware")
				:format(tostring(worst), worstMs)
		end
		return true, ("%d checks timed, slowest %s at %d ms; %d exempt for forcing "
			.. "a synchronous build (%s)"):format(n, tostring(worst), worstMs,
			#builds, #builds > 0 and table.concat(builds, ", ") or "none run")
	end)

	check("The report is assembled in pieces, not one long run", function()
		-- The fix for "script ran too long" is structural, so the check is too:
		-- a chunked builder that quietly stopped chunking would look identical
		-- from the outside until somebody on slower hardware lost the report.
		local D = MM.Diagnostics
		if not (D and D.BuildChunked) then
			return false, "BuildChunked missing — the report runs in one go again"
		end
		if D.inReport then return nil, "skipped inside the report itself" end
		local seen = D.sectionMs and next(D.sectionMs)
		if not seen then
			return nil, "no chunked report built yet this session"
		end
		local n, worst, worstMs = 0, nil, 0
		for name, t in pairs(D.sectionMs) do
			n = n + 1
			if t > worstMs then worst, worstMs = name, t end
		end
		return true, ("%d sections timed individually; slowest is %s at %d ms")
			:format(n, tostring(worst), worstMs)
	end)

	check("The arrow and the plan cannot disagree about where you are going", function()
		-- Reported from play: the guide led with one mount and the arrow with
		-- another, on the same route, at the same moment.
		--
		-- Two causes, and this asks about both. READING where you are heading
		-- used to WRITE the resume anchor, so drawing a panel -- or running
		-- this very report -- re-stamped it; and the anchor is account-wide
		-- while the index is per-character, so an alt inherited a place its own
		-- plan never had. Then a rebuild that found no anchor left the index
		-- pointing into the ROUTE THAT NO LONGER EXISTED.
		local R = MM.Router
		if not (R and R.SetIndex) then return false, "R.SetIndex missing" end
		if #R.route == 0 then return nil, "no route built yet" end
		if not MM.cdb.routeActive then return nil, "no route running to read" end

		-- 1. a read must not move the anchor
		local before = MM.db.routeGoal
		R:Current(); R:Current()
		if MM.db.routeGoal ~= before then
			return false, ("reading the current goal changed the anchor from %s "
				.. "to %s"):format(tostring(before), tostring(MM.db.routeGoal))
		end

		-- 2. the arrow's step IS the route's current step
		local cur = R:Current()
		if cur ~= R.route[MM.cdb.routeIndex] then
			return false, "the current goal is not the stop the index points at"
		end

		-- 3. with nothing to resume to, the route leads from the top
		local savedGoal, savedIndex = MM.db.routeGoal, MM.cdb.routeIndex
		MM.db.routeGoal = nil
		MM.cdb.routeIndex = math.min(5, #R.route)
		-- SYNCHRONOUS, BOTH TIMES. This asked for a chunked build and read the
		-- index on the very next line, so it read the index the PREVIOUS build
		-- left -- the 5 it had just written itself -- and reported the router
		-- for it. The index is published with the route, so the build has to be
		-- waited for.
		R.Invalidate(); R:BuildSync()
		local landed = MM.cdb.routeIndex
		MM.db.routeGoal, MM.cdb.routeIndex = savedGoal, savedIndex
		R.Invalidate(); R:BuildSync()
		if landed ~= 1 then
			return false, ("a rebuild with no anchor left the index at %d rather "
				.. "than leading from the top"):format(landed)
		end
		return true, ("a read leaves the anchor alone, the arrow tracks index %d, "
			.. "and an unanchored rebuild leads from the top")
			:format(MM.cdb.routeIndex)
	end)

	check("A teleport count counts teleports, not stops", function()
		-- The failure this exists to catch is a count that silently equals the
		-- route length, which reads like a measurement and is really just
		-- `#route` wearing a different label. So the assertion is about the
		-- journeys DISAGREEING with the stop count when they should.
		local R = MM.Router
		if not R.ArrivesByTeleport then return false, "ArrivesByTeleport missing" end
		if #R.route == 0 then return nil, "no route built yet to count" end
		local tele, journeysWithoutOne = 0, 0
		for _, stop in ipairs(R.route) do
			if R.ArrivesByTeleport(stop) then
				tele = tele + 1
			elseif stop.arriveBy then
				-- a real travel method that spends no charge: the case the
				-- old existence test could not see
				journeysWithoutOne = journeysWithoutOne + 1
			end
		end
		if tele > #R.route then
			return false, ("%d teleports across %d stops"):format(tele, #R.route)
		end
		if tele == #R.route and journeysWithoutOne == 0 and #R.route > 5 then
			return false, ("every one of %d stops claims a teleport -- the count "
				.. "is tracking the route length"):format(#R.route)
		end
		return true, ("%d of %d stops open with a teleport; %d travel without "
			.. "spending one"):format(tele, #R.route, journeysWithoutOne)
	end)

	check("Whole-plan time never gets worse", function()
		-- The block reorder accepts only improvements, but "only improvements"
		-- is a claim about code that has to be checked against the objective it
		-- claims to improve.
		local R = MM.Router
		if #R.route < 3 then return nil, "route too short to reorder" end
		local _, world = MM.Util.PlayerWorldPos()
		local start = { world = world }
		local now = R.RouteMinutes(R.route, start)
		if not now or now < 0 then return false, "route time did not evaluate" end
		return true, ("whole plan evaluates to %.0f minutes"):format(now)
	end)

	check("Equal-value stops still follow geography", function()
		-- A plan full of unrated 1% drops is a plan full of ties. When ties were
		-- broken by nothing, the chain fell back to whatever order the plan
		-- happened to be in and ignored distance completely -- two entirely
		-- different scenarios produced byte-identical routes.
		local R = MM.Router
		local far  = { world = { x = 30000, y = 0 }, mounts = 0.02, workMinutes = 15, tier = 1 }
		local near = { world = { x = 100, y = 0 },   mounts = 0.02, workMinutes = 15, tier = 1 }
		local chain = R.NearestChain({ far, near }, { x = 0, y = 0 })
		if chain[1] ~= near then
			return false, "took the far stop first when both were worth the same"
		end
		return true, "ties break toward the nearer stop"
	end)

	check("Untangling never lengthens a route", function()
		-- 2-opt reverses whole segments; a sign error there would silently make
		-- every route worse and still look like a route.
		local pts, x = {}, 7919
		for i = 1, 20 do
			x = (x * 1103515245 + 12345) % 2147483648
			pts[i] = { world = { x = x % 10000, y = (x / 7) % 10000 }, ease = (i * 3739) % 10000 }
		end
		local from = { x = 0, y = 0 }
		local before = MM.Router.ChainLength(pts, from)
		MM.Router.TwoOpt(pts, from)
		local after = MM.Router.ChainLength(pts, from)
		if #pts ~= 20 then return false, ("stop count changed to %d"):format(#pts) end
		if after > before + 0.001 then
			return false, ("route grew from %d to %d yards"):format(before, after)
		end
		return true, ("%d -> %d yards"):format(before, after)
	end)

	check("Timed events gate like assaults", function()
		-- the player: Hand of Bahmethra and Mawsworn Soulhunter need a Maw event to be
		-- LIVE. Off-cycle the cache and the boss simply do not exist, and
		-- routing someone there is the Necroray Calling failure again.
		local gated = {}
		for _, rec in pairs(MM.DBByName) do
			if rec.event then
				gated[#gated + 1] = rec.name
				if not (rec.event.mapID and (rec.event.match or rec.event.label)) then
					return false, rec.name .. " has an incomplete event gate"
				end
			end
		end
		if #gated == 0 then return false, "no record is gated on a timed event" end
		-- and the evaluator must refuse to guess, exactly as the assault gate does
		local state = MM.Assaults.Evaluate({ zone = "Nowhere", mapID = -1,
			label = "Nothing", kind = "event" })
		if state == "AVAILABLE" then
			return false, "an unreadable zone reported the event as live"
		end
		return true, ("%d event-gated: %s"):format(#gated, table.concat(gated, ", "))
	end)

	check("Assault gate refuses to guess", function()
		local state = MM.Assaults.Evaluate({ zone = "Nowhere", mapID = -1, label = "Nothing" })
		return state ~= "AVAILABLE", "returned " .. tostring(state)
	end)

	check("Assault gate matches any wording", function()
		-- the map banner and the world quests word the same assault differently,
		-- so a single needle missing must not read as "not running"
		local saved, savedScanned = MM.Assaults.active[-2], MM.Assaults.scanned
		MM.Assaults.active[-2] = { { name = "Assault: The Warring Clans", source = "poi" } }
		MM.Assaults.scanned = true
		local state = MM.Assaults.Evaluate({ zone = "Test", mapID = -2, label = "Mogu",
			match = { "Mogu", "Warring Clans" } })
		MM.Assaults.active[-2], MM.Assaults.scanned = saved, savedScanned
		return state == "AVAILABLE", "returned " .. tostring(state)
	end)

	check("Default weights change nothing", function()
		-- The whole promise of the weights layer: an untouched install must rank
		-- exactly as it did before the layer existed. If this drifts, every
		-- ranking judgement recorded in HANDOFF was measured against a different
		-- addon than the one shipping.
		local W = MM.Weights
		local saved = MM.db.weights
		MM.db.weights = nil
		-- The sliders ARE the planner's coefficients now, so the thing worth
		-- pinning is that they still hold the values the ranking was tuned and
		-- documented against. Every judgement in HANDOFF was measured with these.
		local EXPECTED = { travel = 250, effort = 100, odds = 2500, era = 0,
			priority = 0.6, urgency = 1 }
		local neutral = true
		for _, s in ipairs(W.SLIDERS) do
			if math.abs(W.Get(s.key) - s.default) > 0.001 then neutral = false end
			local want = EXPECTED[s.key]
			if want and math.abs(s.default - want) > 0.001 then
				MM.db.weights = saved
				return false, ("%s defaults to %s, not the documented %s")
					:format(s.key, tostring(s.default), tostring(want))
			end
		end
		local ordered = true
		for i, key in ipairs(W.DEFAULT_ORDER) do
			if W.TierRank(MM.Planner.TIER[key]) ~= i then ordered = false end
		end
		MM.db.weights = saved
		MM:Fire("MM_WEIGHTS_CHANGED")
		if not neutral then return false, "a slider default is not neutral" end
		if not ordered then return false, "tier order does not match the default" end
		return true, ("%d tiers in default order, %d coefficients at spec")
			:format(#W.DEFAULT_ORDER, #W.SLIDERS)
	end)

	check("Every weight explains itself", function()
		-- A raw coefficient with no plain-language reading is a number the player
		-- cannot act on, which is the same as not exposing it at all.
		for _, def in ipairs(MM.Weights.SLIDERS) do
			if type(def.reading) ~= "function" then
				return false, def.key .. " has no reading"
			end
			-- Collected, not written inline: a slider missing `min` would leave
			-- a hole that ends the walk, and this check would pass by never
			-- reading `default` or `max` at all.
			local marks = {}
			for _, v in ipairs({ "min", "default", "max" }) do
				if def[v] ~= nil then marks[#marks + 1] = def[v] end
			end
			if #marks < 3 then
				return false, ("%s does not state min, default and max"):format(def.key)
			end
			for _, v in ipairs(marks) do
				local text = def.reading(v)
				if type(text) ~= "string" or text == "" then
					return false, ("%s reads nothing at %s"):format(def.key, tostring(v))
				end
			end
		end
		return true, ("%d weights, each with a live explanation"):format(#MM.Weights.SLIDERS)
	end)

	check("Reordering priorities takes effect", function()
		local W = MM.Weights
		local saved = MM.db.weights
		MM.db.weights = { order = { "RARE", "PICKUP", "INSTANCE", "FIELD", "REP",
			"GRIND", "ACHIEVE", "GROUP" } }
		MM:Fire("MM_WEIGHTS_CHANGED")
		local rareFirst = W.TierRank(MM.Planner.TIER.RARE) < W.TierRank(MM.Planner.TIER.PICKUP)
		-- blocked can never be promoted, whatever is saved
		local blockedLast = W.TierRank(MM.Planner.TIER.BLOCKED) > W.TierRank(MM.Planner.TIER.GROUP)
		MM.db.weights = saved
		MM:Fire("MM_WEIGHTS_CHANGED")
		if not rareFirst then return false, "reordered list did not change tier rank" end
		if not blockedLast then return false, "BLOCKED was promoted out of last place" end
		return true, "order applied, BLOCKED still pinned last"
	end)

	check("Corrupt weight order self-heals", function()
		local W = MM.Weights
		local saved = MM.db.weights
		MM.db.weights = { order = { "RARE", "NOT_A_TIER", "RARE" } }
		local order = W.Order()
		MM.db.weights = saved
		MM:Fire("MM_WEIGHTS_CHANGED")
		if #order ~= #W.DEFAULT_ORDER then
			return false, ("recovered %d of %d tiers"):format(#order, #W.DEFAULT_ORDER)
		end
		local seen = {}
		for _, k in ipairs(order) do
			if seen[k] then return false, "duplicate tier " .. k end
			seen[k] = true
		end
		if order[1] ~= "RARE" then return false, "the one valid saved entry was not kept first" end
		return true, "junk dropped, missing tiers restored"
	end)

	check("Assault-gated mounts carry a live gate", function()
		local gated, bad = 0, nil
		for _, rec in pairs(MM.DBByName) do
			local g = rec.assault
			if g then
				gated = gated + 1
				if not (g.mapID and (g.match or g.label)) then bad = rec.name end
			end
		end
		if bad then return false, bad .. " has an incomplete assault gate" end
		return gated > 0, ("%d gated"):format(gated)
	end)
end

------------------------------------------------------------
-- Runner
------------------------------------------------------------
-- Run every check a slice at a time, then hand the results over.
--
-- The budget is per FRAME, not per run: nothing here ever occupies the client
-- for longer than one slice, however slow the machine or however many checks
-- are added later. That is the property the watchdog actually cares about.
local SLICE_MS = 25

function T.PrepareAsync(onDone)
	results = {}
	wipe(queue)
	-- THE RECURSION GUARD HAS TO COVER THIS PHASE TOO.
	--
	-- One check builds the whole report to time it, and skips itself when the
	-- report is already building. Running the suite HERE is not "inside the
	-- report" by that flag's reckoning -- so the check built a report, which
	-- ran the suite, which appended a second copy of every result into this
	-- same table. The report came back with 297 checks instead of 193 and no
	-- failure to point at, because nothing had actually failed.
	T.preparing = true
	-- AND SAY SO WITH THE FLAG EVERYTHING ELSE ALREADY WATCHES.
	--
	-- Running the suite here IS running it on the report's behalf, and a dozen
	-- places decide what to do by asking D.inReport. Leaving it false while
	-- doing exactly that work made the RELEASE READINESS section -- which
	-- re-runs the whole suite when no report is building and no run has
	-- finished -- fire from inside the suite. PrepareAsync never sets lastRun
	-- until it completes, so the nested run saw nothing finished either, and
	-- did it again. The client hung with nothing in the log, because nothing
	-- had thrown.
	--
	-- Guarding that ONE site would have left every other reader of this flag
	-- wrong in the same way, including ones not written yet. Setting the flag
	-- is the honest statement and it fixes all of them at once.
	local wasInReport = MM.Diagnostics and MM.Diagnostics.inReport
	if MM.Diagnostics then MM.Diagnostics.inReport = true end
	local function release()
		T.preparing = false
		if MM.Diagnostics then MM.Diagnostics.inReport = wasInReport end
	end
	collecting = true
	local ok, err = pcall(function() runAPI(); runData(); runLogic() end)
	collecting = false
	if not ok then
		record(FAIL, "RUNNER", "self-test crashed while collecting", tostring(err))
		release()
		T.prepared = results
		if onDone then onDone(results) end
		return
	end
	local i, waited = 1, 0
	local function slice()
		-- NOT WHILE THE ROUTE IS BEING REBUILT.
		--
		-- Running the suite in slices means it no longer sees one frozen
		-- snapshot: an asynchronous build mutates R.route BETWEEN slices, so
		-- checks land on a route that is half-finished. That is what produced
		-- "101 of 285 planned goals vanished from the route" and a dozen "no
		-- route" degradations in a report taken while the router was resuming.
		-- The suite was right about what it saw; what it saw was a building site.
		--
		-- Bounded, because a build that never finishes must not swallow the
		-- report. Three seconds and it proceeds, and the check that trips will
		-- at least be reporting on something real.
		if waited < 60 and MM.Router and MM.Router.IsBuilding and MM.Router.IsBuilding() then
			waited = waited + 1
			C_Timer.After(0.05, slice)
			return
		end
		local started = debugprofilestop()
		while queue[i] do
			runOne(queue[i])
			i = i + 1
			if debugprofilestop() - started > SLICE_MS then break end
		end
		if queue[i] then
			C_Timer.After(0, slice)
		else
			-- Handed over rather than printed. Whoever asked for this decides
			-- where the lines go, which is what lets the report collect them
			-- without the suite knowing it is being reported on.
			release()
			T.prepared = results
			if onDone then onDone(results) end
		end
	end
	slice()
end

function T.Run()
	-- THE SUITE MAY NOT RUN INSIDE ITSELF. Ever, by any route.
	--
	-- One check fires every diagnostic section to prove none of them is
	-- silent. One of those sections re-runs the suite when no run has finished
	-- and no report is building. A nested run never finishes before it asks
	-- again, so it asks forever -- and every layer of it is inside a pcall, so
	-- the client hangs with an empty log and nothing to point at.
	--
	-- This has been guarded twice with a flag naming a CONTEXT -- "not while a
	-- report builds", then "not while one is being prepared" -- and both times
	-- a new context appeared that the flag did not describe. The property that
	-- actually matters is not where we are, it is that a run is in progress,
	-- so that is what is asked.
	if T.running then
		return T.lastCounts or { PASS = 0, FAIL = 0, WARN = 0 }
	end
	-- CLEARED EVEN IF THE RUN THROWS. A flag that latches on an error would
	-- leave every later run silently handing back stale counts -- a self-test
	-- that reports yesterday's answer forever and looks exactly like a healthy
	-- one, which is the failure this whole guard exists to avoid repeating.
	T.running = true
	local ok, counts = pcall(T.RunOnce)
	T.running = false
	if not ok then
		MM:Print("|cffff4d4dThe self-test itself threw:|r %s", tostring(counts))
		return T.lastCounts or { PASS = 0, FAIL = 0, WARN = 0 }
	end
	T.lastCounts = counts
	return counts
end

function T.RunOnce()
	-- A prepared run is consumed rather than repeated. Without this the report
	-- would pay for the whole suite twice: once in the background, once inside
	-- the section that prints it.
	--
	-- ONLY INSIDE THE REPORT. `/mm selftest` must always be a fresh run: it is what
	-- someone types to find out whether a change took, and answering it from a
	-- set of results gathered minutes ago would be the exact opposite of what
	-- was asked -- and impossible to notice, because the output looks the same.
	local inReport = MM.Diagnostics and MM.Diagnostics.inReport
	if T.prepared and inReport then
		results = T.prepared
		T.prepared = nil
	else
		results = {}
		local ok, err = pcall(function()
			runAPI(); runData(); runLogic()
		end)
		if not ok then record(FAIL, "RUNNER", "self-test crashed", tostring(err)) end
	end

	local counts = { PASS = 0, FAIL = 0, WARN = 0 }
	local group
	MM:Print("Self-test — %s", date and date("%Y-%m-%d %H:%M") or "now")
	for _, r in ipairs(results) do
		counts[r.state] = counts[r.state] + 1
		if r.group ~= group then
			group = r.group
			MM:Print("|cff9a9a9a%s|r", group)
		end
		-- passes stay quiet unless they carry a useful figure
		if r.state == PASS and not r.detail then
			-- nothing: a silent pass is the expected case
		else
			local colour = r.state == PASS and "ff40d860"
				or r.state == WARN and "ffffd84d" or "ffff4d4d"
			MM:Print("  |c%s%s|r  %s%s", colour, r.state, r.name,
				r.detail and ("  |cffbbbbbb— " .. r.detail .. "|r") or "")
		end
	end
	MM:Print("%d passed, |cffffd84d%d degraded|r, |cffff4d4d%d failed|r  (of %d)",
		counts.PASS, counts.WARN, counts.FAIL, #results)
	-- A SUITE THAT RAN TWICE LOOKS LIKE A BIGGER SUITE.
	--
	-- When the freeze check recursed, every result was recorded a second time
	-- and the report read "282 passed of 297" -- more checks than exist, no
	-- failures, and nothing anywhere saying something had gone wrong. Counting
	-- is the cheapest possible guard against it and it costs one pass.
	local seen, dupes, example = {}, 0, nil
	for _, r in ipairs(results) do
		local k = (r.group or "") .. "\0" .. r.name
		if seen[k] then dupes = dupes + 1; example = example or r.name end
		seen[k] = true
	end
	if dupes > 0 then
		MM:Print("|cffff4d4d%d result(s) recorded twice (e.g. %s) — the suite ran "
			.. "more than once and these totals are not what they appear|r",
			dupes, tostring(example))
	end
	if counts.FAIL == 0 and counts.WARN == 0 then
		MM:Print("|cff40d860Everything this client can do, it is doing.|r")
	elseif counts.FAIL == 0 then
		MM:Print("No failures. Degraded items are checks that could not run — an "
			.. "optional feature this client lacks, or state nothing has produced yet.")
	else
		MM:Print("|cffff4d4dFailures above are real — do not release with these outstanding.|r")
	end
	-- Remembered so /mm release can gate on a REAL run rather than asking the
	-- reader to have looked at the output themselves.
	T.lastRun = {
		passed = counts.PASS, degraded = counts.WARN, failed = counts.FAIL,
		total = #results, at = date and date("%Y-%m-%d %H:%M") or nil,
	}
	return counts
end

-- Into a window, like every other page-length answer. Wrapped at the HANDLER and
-- not around T.Run, because the report and the release gate both call T.Run
-- directly while already capturing -- and a second window opening mid-report
-- would walk off with the section's output.
local function windowed(title, fn)
	local D = MM.Diagnostics
	if not (D and D.Windowed) then return fn() end
	return D.Windowed(title, fn)
end

MM:On("MM_SELFTEST", function()
	windowed("Self-test", function() T.Run() end)
end)

------------------------------------------------------------
-- /mm check — the one command
------------------------------------------------------------
-- Running the diagnostics by hand gives WRONG answers, not just tedious ones.
-- The calendar streams in from the server after a request, Callings arrive on an
-- event, and the bag scan is debounced. Fire those and read them in the same
-- breath and they all report "not available yet".
--
-- So this warms every asynchronous subsystem, waits, and then prints ONE
-- summary with a verdict. Detail stays behind the individual commands; this
-- tells you which of them, if any, is worth opening.
local function count(t) local n = 0 for _ in pairs(t or {}) do n = n + 1 end return n end

-- Public so the diagnostics report can run it without re-doing the warm-up
-- wait; D.Generate already warmed the asynchronous subsystems itself.
function T.RunSync()
	local line = function(label, text, drill)
		MM:Print("|cff33c1ff%-13s|r %s%s", label, text,
			drill and ("  |cff9a9a9a-> " .. drill .. "|r") or "")
	end

	local counts = T.Run()
	local rarityAbsent

	------------------------------------------------------------
	local S = MM.Scanner
	local recs = 0
	for _, rec in ipairs(MM.DBList or {}) do
		if (rec.spellID and MM.DBBySpell[rec.spellID] == rec)
			or (rec.name and MM.DBByName[rec.name:lower()] == rec) then recs = recs + 1 end
	end
	local matched, total = 0, 0
	for _, e in pairs((S and S.byMountID) or {}) do
		total = total + 1
		if e.rec and not e.rec.stub then matched = matched + 1 end
	end
	MM:Print(" ")
	line("DATABASE", ("%d records · %d maps · journal %d/%d catalogued%s")
		:format(recs, count(MM.ResolvedIDs and MM.ResolvedIDs.maps),
			matched, total, total > 0 and (" (%.1f%%)"):format(matched / total * 100) or ""),
		total > 0 and matched < total and "/mm audit" or nil)

	------------------------------------------------------------
	if MM.Rarity.Available() then
		local lib = LibStub and LibStub("MountsRarity-2.0", true)
		local ok, data = pcall(lib.GetData, lib)
		local seen, absent = 0, 0
		if ok and type(data) == "table" then
			for _, mountID in ipairs(C_MountJournal.GetMountIDs()) do
				if data[mountID] then
					seen = seen + 1
					local name, spellID = C_MountJournal.GetMountInfoByID(mountID)
					local rec = (spellID and MM.DBBySpell[spellID])
						or (name and MM.DBByName[name:lower()])
					if not (rec and not rec.stub) then absent = absent + 1 end
				end
			end
		end
		rarityAbsent = absent
		line("RARITY", ("live · %d mounts matched · %s"):format(seen,
			absent == 0 and "|cff40d860none missing from our database|r"
			or ("|cffffd84d%d not in our database|r"):format(absent)),
			absent > 0 and "/mm rarity" or nil)
	else
		line("RARITY", "|cffffd84dMountsRarity not installed|r — difficulty uses our estimates only")
	end

	------------------------------------------------------------
	local plan = MM.Planner:GetPlan()
	local blocked = 0
	for _, entry in ipairs(plan) do
		if MM.QuestGate.HardGate(entry.rec) then blocked = blocked + 1 end
	end
	local stops = #(MM.Router.route or {})
	if #plan > 0 and stops == 0 then
		pcall(function() MM.Router:BuildSync() end)
		stops = #(MM.Router.route or {})
	end
	line("PLAN", ("%d goals · %d stops%s"):format(#plan, stops,
		blocked > 0 and (" · |cffffd84d%d prerequisite-blocked|r"):format(blocked) or ""),
		blocked > 0 and "/mm gates" or nil)

	------------------------------------------------------------
	local carried = count(MM.Acquire.carried)
	line("BAGS", carried > 0
		and ("|cff40d860%d mount%s you can learn right now|r"):format(carried, carried == 1 and "" or "s")
		or "nothing in your bags teaches a missing mount",
		carried > 0 and "/mm bags" or nil)

	------------------------------------------------------------
	local C = MM.Callings
	line("CALLINGS", C.enumerated
		and ("%d read today"):format(count(C.active))
		or "|cffffd84dcould not read today's Callings|r",
		"/mm callings")

	------------------------------------------------------------
	line("EVENTS", ("calendar %s · Timewalking %s"):format(
		MM.Availability.calendarLoaded and "synced" or "|cffffd84dnot synced|r",
		MM.Timewalking.IsActive() and "|cff40d860active|r" or "inactive"),
		not MM.Availability.calendarLoaded and "/mm events" or nil)

	------------------------------------------------------------
	line("TRADING POST", MM.TradingPost.HasLiveData()
		and "live rotation data"
		or "|cffffd84dno rotation data — open the Trading Post once|r", "/mm post")

	------------------------------------------------------------
	------------------------------------------------------------
	-- The verdict has to weigh the SUMMARY, not just the self-test. The first
	-- version reported "everything green" on a run that was also reporting 19
	-- mounts missing from the database and an unsynced calendar, because it only
	-- counted self-test failures. A verdict that ignores the findings above it
	-- is worse than no verdict.
	MM:Print(" ")
	local todo = {}
	if rarityAbsent and rarityAbsent > 0 then
		tinsert(todo, ("%d mount%s missing from the database"):format(
			rarityAbsent, rarityAbsent == 1 and "" or "s"))
	end
	if total > 0 and matched < total then
		tinsert(todo, ("%d journal mount%s uncatalogued"):format(
			total - matched, (total - matched) == 1 and "" or "s"))
	end
	if not MM.Availability.calendarLoaded then
		tinsert(todo, "calendar not synced")
	end
	if not MM.TradingPost.HasLiveData() then
		tinsert(todo, "no Trading Post rotation data")
	end

	if counts.FAIL > 0 then
		MM:Print("|cffff4d4dVERDICT: %d self-test failure%s — not ready.|r",
			counts.FAIL, counts.FAIL == 1 and "" or "s")
	elseif #todo > 0 then
		MM:Print("|cff40d860Self-test clean|r (%d passed, %d degraded). |cffffd84dOutstanding:|r %s",
			counts.PASS, counts.WARN, table.concat(todo, ", "))
	elseif counts.WARN > 0 then
		MM:Print("|cff40d860VERDICT: green.|r %d degraded item%s — checks that could not run.",
			counts.WARN, counts.WARN == 1 and "" or "s")
	else
		MM:Print("|cff40d860VERDICT: everything green, nothing outstanding.|r")
	end
end

function T.FullCheck()
	MM:Print("Running full check — warming the asynchronous bits first...")
	-- Kick these off together; they all need a server round-trip.
	pcall(function() MM.Callings.Request() end)
	pcall(function() MM.Availability.EnsureCalendar() end)
	pcall(function() MM.TradingPost.Refresh() end)
	-- 4s is enough for the calendar and Callings to answer in practice, and the
	-- bag scan debounce is 0.5s.
	-- THE CAPTURE GOES ROUND THE RUN, NOT ROUND THE COMMAND. This returns four
	-- seconds before the suite starts, so wrapping FullCheck itself would capture
	-- the warming line and nothing else, then draw an empty window -- which looks
	-- exactly like a check that found nothing to say.
	C_Timer.After(4, function()
		local crashed
		windowed("Full check", function()
			local ok, err = pcall(T.RunSync)
			if not ok then crashed = err end
		end)
		-- Said in chat as well: a crash the window cannot show is the one thing
		-- worth interrupting for, and the window may not exist to show it.
		if crashed then
			MM:Print("|cffff4d4dCheck crashed:|r %s", tostring(crashed))
		end
	end)
end

MM:On("MM_FULLCHECK", function() T.FullCheck() end)
