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:
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user