The script log called 34 addon packaging bugs "problems" with this client

Every one of the 34 rows a play session reported was read back out of a
capture, and not one was ours: 16 are BetterCharacterPanel anchoring to
Blizzard_InspectUI frames that LoadOnDemand has not built yet, 7 are .toc
and <Script file> lines naming files their own package does not ship, 10
are TitanPanel/InfoBar/Outfitter integrations in addons whose optional
dependencies this player does not have, and 4 are CVars 1.12 never had,
which `cvars`'s own test says to leave unregistered.

The reference shows the player none of that. A missing parent, a missing
relativeTo and an unknown inherits are severity-2 lines in FrameXML.log,
and SetCVar on an unregistered name raises into the pcall every addon
that writes one already wraps it in. Our window was a deliberate superset,
which is right for a compatibility report and wrong for a line reading
"34 problems": a TitanPanel plugin with no TitanPanel is not a fault here.

So `error` (code raised) and `load` (an addon is not running) stay
problems and stay listed, and `warn` -- a call accepted that did not do
what it said -- becomes a note, counted beside them and listed behind a
new Show notes checkbox. Nothing is dropped; the log still carries every
one and the window is one click from all of them.

Also closes a hunt: the 283 `SetOwner: unknown anchor 'BOTTOMRIGHT'`
warnings that three static sweeps of every installed addon could not
place are the realm's own FrameXML, Shop_ProcessEntries forcing items
into the cache through a hidden scan tooltip.

The seven load failures and the two layout defects from the same session
are fixed in the player's own addon copies, mirrored to Addon_Ports; both
are written up in docs/ADDON_COMPAT.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
This commit is contained in:
2026-11-06 15:30:55 -07:00
co-authored by Claude Opus 5
parent 15e1ba1fcd
commit 50a229c202
3 changed files with 249 additions and 10 deletions
+96 -10
View File
@@ -18,6 +18,60 @@
-- A `seq`, not an index: indices shift when the cap evicts.
BenillaScriptLogSelected = nil
-- Of the three kinds, two are things the player can act on: `error` (code raised) and
-- `load` (an addon is not running). A `warn` is a **note**: a call this client accepted
-- and quietly did nothing with, and almost every one is a document naming a frame,
-- template or CVar this installation has not got -- a TitanPanel plugin with no
-- TitanPanel, an `inherits=` on an optional dependency's template, OpenMapClassic
-- switching off a map CVar 1.12 never had. The reference's answer to each of those is a
-- severity-2 line in FrameXML.log or a raise inside the addon's own `pcall`, so the
-- player is shown none of them; counting them as problems made this window read as a
-- list of client faults when it was a list of addons the player does not own. They are
-- one checkbox away, and the log still carries every one.
BenillaScriptLogShowNotes = false
-- Engine row indices, in order, for the rows this view shows.
BenillaScriptLogView = {}
function BenillaScriptLog_IsNote(kind)
return kind == "warn"
end
-- Rebuilds `BenillaScriptLogView`; returns the problem and note counts over every
-- retained row, shown or not.
function BenillaScriptLog_Rebuild()
local retained = BenillaGetNumScriptErrors()
local view, problems, notes = {}, 0, 0
for i = 1, retained do
local _, kind = BenillaGetScriptErrorInfo(i)
if BenillaScriptLog_IsNote(kind) then
notes = notes + 1
if BenillaScriptLogShowNotes then table.insert(view, i) end
else
problems = problems + 1
table.insert(view, i)
end
end
BenillaScriptLogView = view
return problems, notes
end
function BenillaScriptLog_SetShowNotes(show)
BenillaScriptLogShowNotes = (show and true) or false
-- A note read in the detail pane under a list that no longer lists it reads as a
-- stuck window, so turning them off lets the selection go with them.
if not BenillaScriptLogShowNotes and BenillaScriptLogSelected then
for i = 1, BenillaGetNumScriptErrors() do
local seq, kind = BenillaGetScriptErrorInfo(i)
if seq == BenillaScriptLogSelected and BenillaScriptLog_IsNote(kind) then
BenillaScriptLogSelected = nil
break
end
end
end
BenillaScriptLog_Update()
end
-- `strsub` counts bytes, so the cut backs up over UTF-8 continuation bytes (0x80..0xBF).
function BenillaScriptLog_RowText(message)
local cut = strfind(message, "\n", 1, 1)
@@ -45,30 +99,46 @@
function BenillaScriptLog_Update()
local shown, total = BenillaGetNumScriptErrors()
local problems, notes = BenillaScriptLog_Rebuild()
local rows = table.getn(BenillaScriptLogView)
-- "Problems", because two of the three kinds are not errors.
getglobal("BenillaScriptLogNotesCheck"):SetChecked(BenillaScriptLogShowNotes)
-- "Problems", because `load` is not an error either; the notes are counted beside
-- them rather than among them, so the window never reports a TitanPanel plugin with
-- no TitanPanel as a fault of this client's.
local summary
if total == 0 then
summary = "No problems this session."
elseif total > shown then
summary = format("%d problems (%d older not kept)", total, total - shown)
elseif total == 1 then
if problems == 1 then
summary = "1 problem"
elseif problems > 0 then
summary = format("%d problems", problems)
else
summary = format("%d problems", total)
summary = "No problems"
end
if notes == 1 then
summary = summary .. ", 1 note"
elseif notes > 0 then
summary = summary .. format(", %d notes", notes)
end
if problems == 0 and notes == 0 then
summary = "No problems this session."
end
if total > shown then
summary = summary .. format(" (%d older not kept)", total - shown)
end
getglobal("BenillaScriptLogSummary"):SetText(summary)
local scrollFrame = getglobal("BenillaScriptLogListScrollFrame")
FauxScrollFrame_Update(scrollFrame, shown, BENILLA_SCRIPTLOG_ROWS, BENILLA_SCRIPTLOG_ROW_HEIGHT)
FauxScrollFrame_Update(scrollFrame, rows, BENILLA_SCRIPTLOG_ROWS, BENILLA_SCRIPTLOG_ROW_HEIGHT)
BenillaScrollTrough_Follow("BenillaScriptLogListScrollFrame")
local offset = FauxScrollFrame_GetOffset(scrollFrame)
local selectedRow = nil
for i = 1, BENILLA_SCRIPTLOG_ROWS do
local button = getglobal("BenillaScriptLogRow" .. i)
local index = offset + i
local seq, kind, message, count = BenillaGetScriptErrorInfo(index)
local index = BenillaScriptLogView[offset + i]
local seq, kind, message, count
if index then seq, kind, message, count = BenillaGetScriptErrorInfo(index) end
if seq then
button.seq = seq
button.message = message
@@ -221,6 +291,22 @@
<Anchors><Anchor point="TOPRIGHT"><Offset><AbsDimension x="-29" y="-8"/></Offset></Anchor></Anchors>
</Button>
<!-- The notes toggle, on the summary row and right-aligned with the list. Its label
is re-anchored to the left so it stays inside the 293-wide list column. -->
<CheckButton name="BenillaScriptLogNotesCheck" inherits="UICheckButtonTemplate">
<Size><AbsDimension x="20" y="20"/></Size>
<Anchors><Anchor point="TOPRIGHT" relativePoint="TOPRIGHT"><Offset><AbsDimension x="-69" y="-72"/></Offset></Anchor></Anchors>
<Scripts>
<OnLoad>
local label = getglobal(self:GetName() .. "Text")
label:ClearAllPoints()
label:SetPoint("RIGHT", self, "LEFT", -2, 1)
label:SetText("Show notes")
</OnLoad>
<OnClick>BenillaScriptLog_SetShowNotes(self:GetChecked())</OnClick>
</Scripts>
</CheckButton>
<!-- Declared before the rows so their text draws over the glow. -->
<Frame name="BenillaScriptLogHighlightFrame" hidden="true">
<Size><AbsDimension x="293" y="16"/></Size>
@@ -488,6 +488,16 @@ mod tests {
/// `.lua` and `.xml` found only valid `ANCHOR_*` strings, and the addon harness has never
/// reproduced it. Whatever reaches it does so by a path source search cannot see, so the
/// warning must identify itself.
///
/// **It did.** The call site the warning printed is the realm's own FrameXML, not an addon,
/// which is why no addon scan could find it: `Shop_ProcessEntries`
/// (`Interface\FrameXML\Turtle_ShopUI\Turtle_ShopUI.lua:530`) runs
/// `ShopFrameScanTooltip:SetOwner(WorldFrame, "BOTTOMRIGHT")` once per shop entry before
/// `SetHyperlink`, to force each item into the cache. The anchor is dead weight there --
/// the tooltip is a hidden scanner that is never positioned for a player to read -- so the
/// reference's own fallback to mode 0 / `ANCHOR_LEFT` is all the realm ever got from it
/// either, and the warning is a true report of sloppy realm code, not a gap here. The count
/// is one per entry in the shop list.
#[test]
fn an_unknown_anchor_warning_names_the_lua_line_that_passed_it() {
let mut s = UiScript::new().unwrap();
+143
View File
@@ -4990,3 +4990,146 @@ counts are the whole argument:
`GetHighlightColor` looked like the biggest gap at 39 sites and is not a widget method at all:
every one of them is `DBB2:GetHighlightColor()`, DifficultBulletinBoard's own method on its own
table. Counting call sites without checking the receiver is how that kind of row gets written.
## 2026-11-06: the 34 problems in the script log were 34 addon packaging bugs
A play session reported "a bunch of script errors … including 7 addon load failures", with a
screenshot of `/errors` reading **34 problems**, every visible row `BetterCharacterPanel/…`. The
window holds what the session recorded, so the first job was to read all 34 rather than the
thirteen the list shows. A capture over the player's own AddOns folder with an eval that walks
`BenillaGetScriptErrorInfo` printed every row:
```sh
BENILLA_HOME=$SCRATCH/bhome WOW_CAPTURE=ui-spellbook WOW_CAPTURE_ADDONS=1 \
WOW_CAPTURE_SAVED=C_Thun-Gromgosh WOW_CVAR=checkAddonVersion=0 \
WOW_CAPTURE_EVAL=$SCRATCH/dump_errors.lua WOW_CAPTURE_OUT=/tmp/shot.png \
cargo run -q --release -p benilla
```
41 rows offline (the extra seven are the no-server artifacts a capture always adds: `UnitFactionGroup`
nil in HCQuest, `GetXPExhaustion` nil in `ExhaustionTick`, a nil concat in TurtleMail, and the
combat-stats stand-in). **Not one of the 34 was a benilla defect.** The breakdown:
| rows | cause | verdict |
| --- | --- | --- |
| 16 | `BCP_Inspect` parents to `InspectPaperDollFrame` and anchors 45 overlays to `Inspect*Slot` | `Blizzard_InspectUI` is `## LoadOnDemand: 1`, so none of those frames exists when BetterCharacterPanel loads. Faithful: the log line even says so (`is LoadOnDemand — not loaded`) |
| 7 | `.toc`/`<Script file>` lines naming files the package does not ship | BigWigs names `Raids\Other\Nerubian.lua` (only `NerubianOverseer.lua` exists); QuickHeal names `libs\AceDB-2.0\` and four `ItemBonusLib-1.0\Locales\` files it has neither of; Gromissingcrafts' `LibCraftingProfessions-1.0.xml` asks for a `LibStub.lua` that sits one directory up |
| 10 | `inherits=` / `parent=` / `relativeTo=` on TitanPanel, InfoBar and Outfitter frames | optional-dependency integrations in FishingBuddy, QuickHeal and WIM for addons this player does not have. `QuickHealMeleeDPSListEntryTemplate` is never defined anywhere in QuickHeal — an upstream bug, 10 inherits deep |
| 4 | `unknown CVar '…' (not host-registered)` | `worldMapShowPlayerCoords`, `worldMapShowCursorCoords`, `mapFade` (OpenMapClassic switching off three map features 1.12 has not got — see `cvars::tests::the_three_retail_world_map_cvars_stay_unregistered_and_the_write_is_refused`) and `InputDeviceInterfaceStyle` (SoftSpoken probing for the retail gamepad UI) |
### What the reference shows the player for each of these: nothing
The reference logs a missing parent, a missing `relativeTo` and an unknown `inherits` at severity 2
into `FrameXML.log`, and answers `SetCVar` on an unregistered name by **raising**
`Couldn't find CVar named '%s'` (`0x842310`) — which every addon that writes one catches, because
they write them inside a `pcall` precisely expecting absence. None of it reaches `_ERRORMESSAGE`,
so none of it reaches a screen. Our script log is benilla's own window and was a deliberate
superset, which is right for a compatibility report and wrong for the line that says
**"34 problems"**: a TitanPanel plugin with no TitanPanel is not a problem with this client.
So the three kinds now split two ways in `ScriptLogFrame.xml`. `error` (code raised) and `load`
(an addon is not running) are **problems** and are listed. `warn` — "a call was accepted and did
not do what it said" — is a **note**, counted beside the problems in the summary and listed only
when the new **Show notes** checkbox is ticked. Nothing is dropped: the log file still carries
every one, collapsed by shape, and the window is one click from all of them.
### The seven load failures, fixed where they live
Each of the seven is a line in a third-party package naming a file that package does not contain,
so each is fixed in the player's own copy and mirrored to `Addon_Ports/`:
- `bigwigs_port` — `Raids\Other\Nerubian.lua` commented out of the `.toc`.
- `quickheal_port` — the `AceDB-2.0` line and the four `ItemBonusLib-1.0\Locales\` lines commented
out of the `.toc`. Nothing in QuickHeal references AceDB.
- `gromissingcrafts_port` — `<Script file="LibStub.lua">` → `<Script file="..\LibStub\LibStub.lua">`.
A `<Script file>` resolves against its own document's directory, which is the rule the reference
uses too; without it *every handler in that file is missing* and the addon then raised twice at
load (`LibCraftingProfessionsConstants` nil, `LibCraftingProfessions` nil). Both raises are gone.
Note XML forbids `--` inside a comment, and our parser says so: a first attempt at this edit
failed with `malformed FrameXML document: comment at 2:5 contains '--'`.
### BetterCharacterPanel's inspect half never ran, on any 1.12 client
`BetterCharacterPanel.xml` builds `BCP_Inspect` under `InspectPaperDollFrame` and anchors 45
enchant overlays to the fifteen `Inspect*Slot` buttons, and `-paperdoll.lua` captures
`InspectPaperDollFrame_OnShow` into `_Orig_` at file scope. All of it is `Blizzard_InspectUI`'s,
and that addon is LoadOnDemand, so at BetterCharacterPanel's load time the parent is nil, the 45
anchors miss, `_Orig_` is nil, and the hook the addon installs is overwritten the moment
Blizzard_InspectUI *does* load. The addon cannot declare `## Dependencies: Blizzard_InspectUI`
either — that would disable it, since a LoadOnDemand dependency is never loaded at startup.
`bettercharacterpanel_port` adds three lines at the top of `BetterCharacterPanel-lib.lua`, which
the `.toc` runs before `-paperdoll.lua` and before the `.xml`:
```lua
if not IsAddOnLoaded("Blizzard_InspectUI") then
pcall(LoadAddOn, "Blizzard_InspectUI")
end
```
All 16 rows go, and the inspect panel's enchant overlays start working for the first time.
### The stats column could not be scrolled to its bottom
Same session, same panel: the right-hand stat cards stopped mid-`Defenses vs Boss` at the bottom of
the bar's travel. Measured on the live panel:
| | |
| --- | --- |
| `BCPPFUIStatsScrollFrame:GetHeight()` | 359 |
| `BCPPFUIStatsContent:GetHeight()` | 699 |
| `BCPPFUIStatsScrollBar` max | 340 |
| `BCPPFUIStatsScrollFrame:GetVerticalScrollRange()` | 388 |
The bar's maximum is derived from the content height two lines after it is set, so 699 *is* the
limit — and the content is not 699 tall. `BCP_BuildScrollContent` ends its eight-card loop with
`yOffset = 744` and then declares `contentFrame:SetHeight(yOffset - 45)`. The last card's bottom is
at 741 (`yOffset` minus the 3-unit gap the loop adds after every card, the last one included), so
the declared height was 42 short and the bar stopped 42 short of the end. The engine's own 388 is
not the deviation it looks like: `scroll_range` unions the child's whole subtree
(`subtree_span`, the reference's `0x786f80`), and the pfUI card backdrops hang ~6 units past the
content frame.
`yOffset - 45` → `math.max(0, yOffset - 3)`. Content 741, bar max 382, and at full travel the last
card's bottom lands on the window's bottom.
### OctoPawn's OP Score was printed inside the title dropdown
`OctoPawn/UI/Paperdoll/Paperdoll.lua` seats its score frame at `TOP, PaperDollFrame, TOP, 0, -57` —
stock 1.12's free row under the name and level. This realm puts `PaperDollFrameTitlesDropdown`
there, and pfUI-turtle's Character skin re-seats it under the guild line, so the score landed in
the middle of the title box.
Measured off the player's own screenshot rather than a capture, because a capture has no titles and
the dropdown is hidden there: the pfUI backdrop's top and bottom border rows are the only two rows
in that band where all 321 sampled pixels are bright, at y=152..154 and y=181..183, giving a 31 px
box for a 25-unit backdrop — 1.24 px/unit, which the 12 px tall 9.69-unit level FontString confirms.
The ink of the level line ends at y=123, so the free gap is 29 px = **23 units**, against the 11
units one line of `GameFontNormalSmall` needs.
The anchor is now `TOP, CharacterLevelText, BOTTOM, 0, -3` with the holder at 11 units, taken off
the level text rather than off the dropdown: the dropdown's own top moves with the guild line,
present or not, and this gap does not. A capture cannot check it, and reads only 4.7 units of gap,
because with no server there is no title, `CharacterGuildText` collapses to a 1-unit box and the
dropdown rides up with it — but anchoring to the level text is the safe side of that
disagreement, since it clears the level line in *both* layouts where anchoring to the dropdown
would only clear it in one.
**A rect is in its own frame's scale.** `PaperDollFrameTitlesDropdown:GetEffectiveScale()` is 0.9,
so its `GetBottom()` of 747.8 is 673.0 in the UIParent units every other frame here reports, and
comparing the two raw looked for a while like the window drawing 75 units from where it said it
was. It is not: guild bottom 703.65 minus the skin's `-2` *in the dropdown's own units* (1.8
screen units) is 701.85, which is exactly `779.83 × 0.9`. Our resolver crosses the scale boundary
correctly; the probe did not.
### The `SetOwner: unknown anchor 'BOTTOMRIGHT'` hunt is over
A long-standing diagnostic (`tooltip::verbs`) existed because a real session emits 283 of
`SetOwner: unknown anchor 'BOTTOMRIGHT' on owner 'WorldFrame'` and three static sweeps of every
installed addon failed to find the caller. It printed its own call site this session, and the
caller is not an addon at all — it is the realm's own FrameXML:
`Shop_ProcessEntries` (`Interface\FrameXML\Turtle_ShopUI\Turtle_ShopUI.lua:530`) runs
`ShopFrameScanTooltip:SetOwner(WorldFrame, "BOTTOMRIGHT")` once per shop entry before
`SetHyperlink`, to force each item into the cache. The tooltip is a hidden scanner that is never
positioned for anyone to read, so the reference's own fallback to mode 0 / `ANCHOR_LEFT` is all
that code ever got from it either. A true report of sloppy realm code, not a gap here.