Releases: HandofDevastation/HoDLootAdvisor
Releases · HandofDevastation/HoDLootAdvisor
Release list
v2026.08.29-1626
Loot Advisor
v2026.08.29-1626 (2026-08-29)
Full Changelog Previous Releases
- The weekly data refresh never published to CurseForge
It had NO CurseForge token at all, and carried GITHUB_OAUTH on the step rather
than the job. The packager treats a missing token as "do not publish there" -
no error, exit 0 - so today's data release went to GitHub and skipped CurseForge
in silence. Exactly the failure that cost two days in Session 253, which was
fixed in release.yml and never here.
Left alone this was worse than a one-off: CurseForge would have received the
addon's CODE on every tag and its DATA never, so the season's loot table would
have frozen there at whatever shipped first while GitHub users updated weekly.
Job-level env, both token names from the one secret, and the same plain-words
credential check release.yml carries - because the only honest signal is a line
saying the token arrived.
v2026.08.29-1614
Loot Advisor
v2026.08.29-1614 (2026-08-29)
Full Changelog Previous Releases
- Give a wrapped line room, and drop the spacer under the title
THE WRAP. Both sentences in the Import Raid Night tooltip fit - 297.7px and
295.8px in General Sans at 11, summed from the bundled TTF rather than guessed -
and both wrapped anyway inside a 300 ceiling, because the client's text metrics
and the font's advance widths disagree by a fraction. The same 0.7px margin that
truncated a column header in Session 252. Two changes: the ceiling is 360, which
clears both with real room while the Loot Log's 569.5 and the targeting note's
437.4 still fold; and a wrapped line is now given its measured width plus slack,
because handing one exactly its own width is what wraps it.
THE GAP UNDER THE HEADER was a blank spacer line. Callers open with one because
GameTooltip's heading sits tight against its body and needs it; ours already
carries its own gap, so the two stacked into ~20px of nothing. Dropped at the
widget, so a tooltip written from muscle memory cannot reintroduce it, and the
two now-pointless calls removed from the score breakdown. Spacers BETWEEN
sections are untouched.
smoke 596 with both guards revert-checked. - The tooltip threw on a double line, and the vault control moves above the dropdown
WHY NOTHING DREW. AddDoubleLine never set a wrap flag, so nil reached
SetWordWrap, which throws - and an error inside OnEnter aborts the handler
before anything is drawn. The tell was WHICH tooltips survived: every one whose
lines pass wrap=true explicitly worked (Usable Only, Import Raid Night, Loot
Log) and the only one built from double lines, the score breakdown, did not.
Fixed at the source and coerced at the setter, so no caller can reintroduce it.
THE VAULT CONTROL sat on the tab row, growing leftwards from the dropdown into
108px of clearance that this file measured carefully and that did not hold -
Jason saw it overlapping the Runner tab, two mouse-enabled frames on the same
pixels, which is exactly what the measurement existed to prevent. It now sits
ABOVE the dropdown with right edges flush, which is where Jason's Figma frame
has it; the previous position was never the design's. The stale budget comment
went with it rather than being re-tuned - the mock's fourth tab would have
collided again. - Our own tooltip, in our own type, and no standings without a raid night
THE FONT (Jason: "nothing in this addon should be anything other than General
Sans or Khand"). Every explanatory tooltip was drawn on Blizzard's GameTooltip,
which renders in the CLIENT's tooltip font - so the one surface the design never
reached was the surface that explains the design. Setting a font on that frame
was never an option: it is shared with the whole UI and every other addon, and
restyling it restyles all of them.
Tip.lua is a small tooltip of our own - panel ground, hairline rim, Khand title,
General Sans lines - with GameTooltip's method names, so migrating fourteen call
sites was a change of identifier rather than fourteen chances to introduce a
difference. Its ARITHMETIC lives in ns.TipLayout in Core.lua, because Tip.lua is
a window file the harness cannot load and this project's rule is that logic must
be testable. Nine checks cover it, including the mistake that would let a label
and its value touch on the widest row.
⚠️ ITEM TOOLTIPS DELIBERATELY STAY ON GameTooltip - the two SetHyperlink sites.
That is the GAME's item card, which we could not reproduce and should not try
to, and Tooltip.lua appends the TARGETED line to it so the flag reaches bags,
vendors, chat links and the Adventure Guide.
NO STANDING WITHOUT A RAID NIGHT. EPGP arrives only in the export, so with
nothing imported there is no ladder, no priority and no rank - which is every
install outside this guild. The header's standing block and the Standings tab
both hide, together, since either one alone would contradict the other. An
em-dash there read as a number we failed to find rather than a question that
does not apply. The scoring half is untouched and still works in full.
smoke 594, comms 178, roster 103, parity, package 20; all files compile under
luajit. - Centre the facts line, and size the item icon to the text beside it
MEASURED, NOT NUDGED (the S252 rule about measuring rather than eyeballing,
applied to boxes instead of strings).
The facts line sat 3px below one hairline and 10 above the other. The band
between them is 24 tall and the text box measures 10.94, so centred is 212.5,
taken down to 212 because the box carries descender space the glyphs do not
fill and the ink sits high within it.
The item icon was 40 and the name and sub-line beside it occupy 243 to 268.94 -
centre 255.97 - so the text read high against it. Jason's call: shrink the icon
to the text rather than move the text. At 32 it spans 240 to 272 and centres
them with 3.00 above and 3.06 below. div2 sits 10 above the block, so div3 now
sits 10 below it at 282 rather than 290, and everything under it moves up the
same 8 - so the gap from div3 to the ranking heading is 12 exactly as before,
and the 8px is reclaimed at the bottom of the pane rather than left as a hole.
The icon size is one constant now; its hover region reads the same one. - A tier token says so, and the slot line is right the first time
TWO FAULTS JASON REPORTED AFTER THE LAST PASS.- A TIER TOKEN STILL DREW NO SLOT. The label added last commit was never
reached: the Adventure Guide answers "" for a token's slot, "" IS TRUTHY, so
it beat our payload's real answer (tokenSlot=HANDS) and rendered as nothing.
The third time this family has bitten in one session, after the blank item
name and the fallback inside it - so it is now a named function, ns.NonEmpty,
applied at every seam where a value arrives from the client, rather than a
rule to be remembered case by case.
The line now reads "Hands, Tier Token": the slot still leads, because a token
is FOR a slot, and the kind follows, because the slot alone reads as an
ordinary armour piece which is the one thing it is not. - THE SECOND LINE CHANGED AT ALL, which was the actual complaint - not the
wording. It changed because the FIRST draw ran against a COLD Adventure Guide
and fell back to our own payload, and the warm read replaced it a beat later.
Journal.PrewarmSeason reads every boss on entering the world, staggered, so
the cache is warm minutes before anyone presses anything and the panel's first
draw is also its final draw. Not at panel open: that is the moment being fixed.
HARNESS. The prewarm coalesces with the warm pass the journal test observes, so
that test now drains the queue instead of discarding it - which is what happens
in game anyway. The assertion is unchanged and still bites: with the booking
removed it fails, verified both before and after.
smoke 585, comms 178, roster 103, parity, package 20; all three files compile
under luajit.
- A TIER TOKEN STILL DREW NO SLOT. The label added last commit was never
- One vocabulary for the slot line, and the repaint fix everywhere
THE FLICKER Jason reported: the row's second line is drawn from OUR payload
while the Adventure Guide is still cold, then redrawn from the Guide half a
second later - so it read TRINKET and then Trinket, SHOULDER then Shoulder,
and OFF_HAND then Held In Off-hand. Both right; one flicker. ns.SlotLabel maps
our keys into the game's own wording so both sources produce the IDENTICAL
string and the switch becomes impossible rather than merely quick. It mirrors
JOURNAL_SLOT, which already translates the other way; they are two halves of
one translation. Unmapped values pass through untouched, which is what makes it
safe to run over the Guide's own labels.
TOKEN had no word at all, so a tier token drew its badge beside an empty second
line (Venomwoven Effigy). It is a slot key we invented, so we supply the label.
THE REPAINT FIX GENERALISED. Every recycled row type wrote its text and THEN
showed itself; all four now show first, and the name - the field most likely to
repeat across refreshes, which is the case that cannot recover - goes through
setTextForce. Weapons still refine from One-Hand to Dagger on the warm read:
that is the Guide knowing a subtype we never imported, a better answer arriving
rather than two words for one fact.
Scaffolding removed: the fontstring probe, the per-draw column note and
DescribeNames, replaced by tests for the behaviour that came out of them.
smoke 578, comms 178, roster 103, parity, package 20. Both files compile under
luajit, which is the 5.1 the game runs. - An unchanged string is never repainted, so a failed first paint is permanent
The oldest recurring complaint about these addons - "it only appears after I
close and reopen, or switch view" - measured at last.
Two draws logged in the same second on a cold client. The item name and the
verdict beneath it are the same font, same colour, same row, written four
lines apart. Both reported vis...
v2026.08.29-1420
Loot Advisor
v2026.08.29-1420 (2026-08-29)
Full Changelog Previous Releases
- Show the inspection sweep's progress, which nothing was reporting
"N of M Reporting" counts people BROADCASTING over addon comms — who else has
this addon — and reads 0 in a group of strangers however well the sweep is
going. Jason read it as inspection progress, which is exactly what it looks
like when nothing else reports that.
ns.InspectionSummary counts what the sweep has actually resolved out of who is
standing here, from any source: a self-report and an inspection both count,
because a self-report is a better answer than an inspection rather than a
missing one. Yourself is excluded — you are never inspected, and counting
yourself makes a solo "1 of 1" that means nothing. The footer now shows both
figures, which are two different questions.
Confirmed while here, and it is codified: NeedsInspect does NOT exempt people
in the payload, so export raiders are inspected like anyone else; and
Payload.SlotState resolves four tiers with an inspection beating the snapshot
on recency. The export's gear is only as fresh as someone's last logout.
Harness: roster 100 -> 103, revert-checked.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com - Rank only who is present, open on modern loot, and un-stick the Content control
RANKING IS THE GROUP, FULL STOP (Jason, correcting my first attempt). I built a
fail-open exception for "the scan has seen nobody yet", reasoning an empty list
reads as broken. His answer: an empty list is CORRECT — joining an LFR should
show nobody and backfill as the inspect passes land. Showing the last-imported
raid team instead IS the clutter being complained about. The only fail-open
left is the honest one: out of a group, show the whole roster, because browsing
solo is planning and there is no "here" to filter against.
AUTO-OPEN WAS WIRED TO AN EVENT MODERN GROUP LOOT DOES NOT FIRE. The panel's
only opener was START_LOOT_ROLL — the legacy roll system. Retail group loot
arrives through C_LootHistory, so a live LFR recorded eighteen drops with the
setting ON and the window never appeared. Factored into Loot.AutoOpen and now
called from the loot-history path too, on a brand-new drop only so the
four-minute rescan cannot fight someone who closed it. It records its decision
on every branch INCLUDING "setting is off", because a feature whose failure
mode is silence must not be debugged from a silent log.
⚠️ I called this "not a bug" from the setting's default without checking
whether he had turned it on. He had.
THE CONTENT CONTROL RETURNS TO AUTO ON ENTERING AN INSTANCE. It read "Raid:
Heroic" through an entire LFR, scoring every item at levels that wing cannot
drop. A manual pick still wins and still sticks; it resets only at the boundary
where the old choice is knowably stale, and says so rather than changing
silently.
⚠️ TWO FIXTURES NEVER MODELLED THE TEAM BEING IN THE RAID, which is what the
new ranking rule depends on, so they failed for want of a raider rather than
for anything they tested. Both now put export raiders in the instance — and
roster.lua takes one back OUT, so the filter is exercised rather than vacuous.
One assertion encoded the rejected behaviour outright (#effective > base, the
whole export plus everyone here) and now asserts the opposite.
Harness: smoke 560, roster 100. Presence filter revert-checked (24 of 24).
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com - Match drops on either encounter id, and rank only who is in the group
CURRENT DROPS NEVER WORKED IN A RAID. The boss strip keys on the journal
encounter id; a recorded drop carries the DungeonEncounter id from
ENCOUNTER_END. Entombed Sentinels is 2874 in one space and 3445 in the other,
so the filter matched nothing and the list read "NO DROPS CURRENTLY" through a
full LFR with eighteen drops recorded. The payload now ships both and
EncounterIdsFor accepts either.
⚠️ THE HARNESS ASSERTED THE BUG. "a raid tile is still a single boss id, not a
set" passed for months, because the belief was written into the test and the
code together — the S244 trap, verbatim. It now asserts both id spaces, with a
fixture that supplies the kill id so it can actually fail.
RANKING NOW FOLLOWS WHO IS PRESENT (Jason). The roster is the raid TEAM, not
the people standing here, so an LFR ranked twenty-two strangers alongside
sixteen guild raiders who were at home — "31 of 38 raiders gain from it". On a
guild night the same fault is quieter and worse: someone who did not turn up
outranks someone who did.
⚠️ IT FAILS OPEN. If the group scan has seen NONE of the roster we do not
filter at all: zero matches means no evidence about who is here, not that
nobody came, and an empty ranking reads as "nobody wants this" rather than as a
broken lookup. Same reasoning as guildMemberUserIds on the site. The roster
harness caught exactly this — the first version emptied the list.
Both revert-checked. Harness: smoke 560, roster 97 -> 99.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com - Close the gap under the rail headings that carry no figure
Every rail block used one set of offsets: heading, then a 34px slot for the big
orange figure, then the text lines at +54. Priority ("#3") and Attendance
("3/4") fill that slot. Earned/Spent and Last Item Won have no figure and
reserved it anyway, so their values hung a figure's height below their own
heading with nothing in between — the two gaps Jason marked against the Figma
frame, where each value sits immediately under its label.
Compact blocks now start their lines at +20, clearing the 16px heading. Line
pitch is unchanged at 16, and the block origins are untouched, so the section
rhythm down the rail is the same.
Panel.lua is a window file and the headless harness does not load it, so this
is verified by eye in game; it does compile under Lua 5.1 (luajit -bl), which
is the check that matters most in this file.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com - Export the winner's realm, and colour the ladder from the ladder
Two same-named characters on different realms cannot be told apart from a bare
name, and the site refuses to guess — so every drop Abirn won this season went
in attached to nobody and four contested need wins were never charged.
Record.lua now resolves the realm when the roll window omits it, using the
group roster (UnitName's own answer), and leaves the name BARE when the roster
has never seen that person. A bare name here does not mean "the reader's
realm": measured across 125 recorded wins the roll window carried a realm 43
times and omitted it 82, with fourteen players appearing both ways on the same
night — so stamping GetRealmName() would have invented "Abirnn-Stormrage" for
an Area-52 character.
StandingsRows takes class from the ladder rung itself, falling back to the old
roster join. The ladder was named after the PERSON while the roster is keyed by
CHARACTER, so anyone whose display name differed from the toon they raid on
matched nothing and rendered white with an em-dash — five of seventeen.
Harness: smoke 557 -> 559. Both halves of the realm fix revert-checked, as is
the ladder class. The guild-run fixture now scans a real roster so the export
it hands the site's parser carries realms, as production does.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com - Build the Runner tab from the mock, and move the score breakdown onto the score
THE RUNNER TAB WAS THE LAST SURFACE STILL RENDERING THE OLD TEXT REPORT — a
scrolling list of lines inside the redesigned pane. It is now the mock: a left
RAIL of state (who is running loot and since when, what is loaded, and the two
controls that change it) beside a column of the detail a raid night actually
needs, split by a full-height hairline — the same shape the Standings tab uses,
which is what makes the two read as one design.
Positions are read off the Figma frame rather than eyeballed: the mock's origin
is (2383, 779) and every constant is a node position minus that.
THREE STATES, NOT TWO. "You are running loot", "someone else is", and "nobody has
claimed it" want different actions, and the third reads as broken if it is folded
into the second. The legacy-payload case REPLACES the lead line rather than
sitting under it, because it makes that line untrue: a roster pasted before comms
loaded cannot be shared at all, and every other line on the tab looks healthy.
Data the tab needed, added to Comms.RunnerReport where it can be tested rather
than to the panel where it cannot: the export's own age beside the gear age (two
numbers, never conflated — the gear-age rule reaching a new surface), when our
own claim started, and per peer whether their gear is actually ARRIVING and
whether their build differs. "Running the addon" and "reporting gear" are
different states with different fixes, so they get diff...
v2026.08.29
Loot Advisor
v2026.08.29 (2026-08-29)
Full Changelog Previous Releases
- Loot data refresh v2026.08.29
- Stage the no-kill-id case instead of inheriting it from the payload
The data refresh got further and the harness caught the next thing: a check for
the FALLBACK path - a boss with no kill id matching on its journal id alone -
restored the boss's ORIGINAL value and then asserted there was no id. That held
only while the emitted payload carried none. The emitter now ships enc for all
nine bosses, so the restore put a real id back and the check failed, having
never tested that path on its own terms.
The S251 rule, in its quieter form: a fixture that differs from production in a
field the code branches on tests a system that does not exist - and this one was
AGREEING with production by accident rather than staging its own condition. It
now clears the id explicitly and restores afterwards.
Verified against the REAL new payload, fetched from the same URL CI uses, under
Lua 5.4: smoke 596, comms 178, roster 103, parity. - The harness did not compile under the Lua that CI runs
The data refresh failed before it fetched anything: smoke.lua declared 189
top-level locals against Lua's ceiling of 200, and the generic for-loop at line
2012 needed the registers that tipped it over. NOT introduced today - every
commit back through yesterday's released tag fails the same way. It went unseen
because only data-refresh runs the harnesses in CI and it last ran on the 26th;
release.yml packages without running them at all.
⚠️ THE SAME TRAP THIS PROJECT ALREADY WROTE DOWN, one runtime along. Session 250
learned that luac on this machine is not the Lua the GAME runs; this is that
again between this machine and CI. luajit compiles the file, 5.5 runs it, and
5.4 refuses it - so 'the tests pass' was true locally and false where it counts.
lua@5.4 is now installed here, and the harnesses are verified under BOTH.
THE FIX IS THE FILE'S OWN CONVENTION: nine sections whose locals never escape
them are now wrapped in do...end like the sections around them, freeing 40
registers. No assertion changed; 596 checks pass under 5.4 and 5.5 alike.
v2026.08.28-0435
Loot Advisor
v2026.08.28-0435 (2026-08-28)
Full Changelog Previous Releases
- Point the packager at CurseForge project 1671489
Pairs with the CF_API_TOKEN repo secret. With both present a version tag now
uploads to GitHub AND CurseForge in one run; remove either and the CurseForge
step is skipped while the GitHub Release still gets cut.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com - Wire the release workflow for CurseForge
WoWUp's GitHub provider is broken for UPDATES -- it fetches the API asset
URL without the octet-stream header and unzips GitHub's JSON metadata
(WowUp.CF #103 / #107). Open since February, fix written but unshipped. The
only per-user workaround is a GitHub personal access token, which is not a
thing a guild can ask of its raiders.
The WowUp Hub was investigated and REJECTED ON EVIDENCE: walking its API by
id shows THIRTY-THREE GitHub-source addons in the entire catalogue, ids 1-50
with gaps and nothing above 50 -- WeakAuras is id 10. The thousands of
"WowUpHub" entries people see are CurseForge and Tukui content proxied
through it, which the Hub's own page says outright. There is no self-serve
submission route and roughly one addition a month.
CurseForge is what WowUp-CF is actually built around, so a listing there
makes search, install and update work with nothing asked of the person
installing. This reverses the standing GitHub-Releases-only decision for
distribution only; nothing about how the addon is built or tagged changes.
⚠️ INERT UNTIL TWO THINGS EXIST: the CF_API_TOKEN repo secret and the
numeric project id on the .toc. The packager skips any destination whose
token is missing, so tagging a release keeps cutting a GitHub Release
exactly as before until both are in place.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
v2026.08.28-0342
Loot Advisor
v2026.08.28-0342 (2026-08-28)
Full Changelog Previous Releases
- Six defects from the first real raid night
THE BOSS STRIP DID NOTHING ON CURRENT DROPS. It only ever filtered the full
loot table; the drops list returned every drop of the night whichever
portrait was selected, so clicking through the strip changed nothing and
the tab looked frozen on the last kill. Every recorded drop already carries
its journal encounter id -- the same id space the strip keys on -- so
Record.RecentDrops now takes a boss and compares directly. A boss with no
kill tonight comes back EMPTY and shows NO DROPS CURRENTLY: showing another
boss's loot under its portrait is worse than showing none.
"N OF M RAIDERS CAN USE IT" WAS A MISLABEL. That count is raiders the item
is an UPGRADE for, not raiders who can equip it -- the two differ by
everyone it fits and does not improve, which on a geared roster is most of
them. It reads "gain from it" now. And when the viewer is not in the list
the panel SAYS SO ("you are not listed -- no gain for you"): the table holds
only people the item improves, so a viewer it does not improve simply is
not there, which reads as the addon having lost them. The header said "No
Upgrade" three inches away and nobody connects the two.
AUTO-OPEN NOW HAPPENS FIRST, not last. It sat at the END of the roll
handler, behind scoring, chat reporting, the ranking, the recorder and a
comms broadcast -- and the whole handler runs inside a pcall, so a throw
anywhere in that chain silently took the panel with it. The symptom is a
window that opens on some kills and not others for reasons unrelated to the
kill. Two causes were proposed for that from the armchair and BOTH WERE
WRONG; this does not diagnose it, it makes it not matter. pcall'd on its own
so a panel fault cannot break scoring and recording either.
PATTERNS AND HOUSING DECOR ARE NOT LOOT. The Encounter Journal enumerates
everything an encounter drops and in 12.1 that includes profession recipes
and decor, which arrived as UNSCORED rows -- the addon truthfully having no
opinion about a leatherworking pattern, on a list that answers "who is this
for".⚠️ ns.IsGearItem tests the GAME'S item class and never our own
ignorance, because Data Contract §0 requires an item we never imported to
still appear: unknown ARMOUR and WEAPONS stay, recipes and decor go.⚠️ AND
TIER TOKENS ARE "MISCELLANEOUS" TO BLIZZARD, the same class as the decor, so
anything in our own payload counts as gear whatever its class -- without
that clause this silently drops every tier token. Fails open.
THE DIFFICULTY CONTROL IS BACK, as the dropdown Jason has since drawn at
x=500 on the tab row. It was cut in the rebuild when no design had it, which
left no way to tell which difficulty a loot table was being shown for. On
AUTO it reports what auto RESOLVED to ("Auto: Heroic"), since that is the
actual question.
THE RAW ROLL STATE NOW RIDES BESIDE ITS LABEL in the loot log. The first
real group-loot night recorded state 0 on 125 rolls that ALL carried a real
roll value, and we map 0 to "noroll" -- so the inherited HoDLootTracker map
is wrong, and transmog never appears at all. The map is NOT changed on that:
one night does not say what 0, 1 and 2 ARE, and the standing rule is to wait
for evidence rather than guess. Printing the number where a human can read
it against the game's own roll window is how the next raid settles it.
Harness: smoke 392 -> 425. New: the boss filter and its empty case, the
gear test including the token carve-out and the fail-open, and auto-open
surviving a throw below it. Every one revert-checked.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
v2026.08.28-0055
Loot Advisor
v2026.08.28-0055 (2026-08-28)
Full Changelog Previous Releases
- Panel.lua did not compile in the game: 60 upvalues in Lua 5.1
/la answered "panel did not load" while the rest of the addon worked, so
nothing pointed at the panel. ns.Panel is assigned on the third line of the
file, which means the file never COMPILED -- and it never compiled because
build(), at 443 lines, closed over more than 60 upvalues. That is a hard
limit in Lua 5.1, which is what WoW runs. 5.2 raised it to 255.
Every file-scope constant a function references costs one upvalue, and the
redesign added roughly fifty geometry constants to a builder that already
closed over dozens. A limit a growing function crosses silently.
⚠️ luac -p PASSED, AND PROVED NOTHING. The luac on this machine is 5.5,
where the same file is legal. "It compiles" was checked against the wrong
language the entire session. The harness now compiles EVERY file with a
real 5.1 parser (luajit -bl) and reports a SKIP, loudly, when none is
installed -- so a missing check can never be mistaken for a passing one.
Revert-checked: restoring the single build() fails that check and only
that check.
build() is now six functions along its existing section boundaries --
chrome, loot controls, standings, detail pane, footer, per-tab controls --
each closing over its own section's constants, so adding a control to one
cannot silently re-break the others. No behaviour changes; the split is
along lines the file already drew for itself.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
v2026.08.28-0023
Loot Advisor
v2026.08.28-0023 (2026-08-28)
Full Changelog Previous Releases
- Rebuild the panel from the design: Loot and Standings
THREE TABS OVER A TWO-COLUMN READING SURFACE, replacing six tabs over a
horizontal chip strip. The panel is an OUT-OF-COMBAT REFERENCE -- nobody
has it open during a pull -- so width is nearly free and the layout
optimises for reading. The chip strip existed to fit five drops into a
narrow window; with the width available the items become a vertical
selector that holds a whole loot table instead of five chips and a pager.
What did NOT change: the two rankings are still different SHAPES. EPGP
priority is one global list, stable through a kill; upgrade magnitude is a
different list per item. The detail pane still orders by upgrade with
priority as a COLUMN, and the full ladder is still its own tab.
LOOT TAB -- boss strip, item column, detail pane, and a fixed bottom bar
carrying Import Raid Night on EVERY tab (it is how a non-runner becomes
the runner, so it cannot live behind the tab that only appears once you
already are one). Post is runner-only: two people posting puts two lists
in raid chat for one item, and chat is all a non-installer ever sees.
STANDINGS TAB -- the EPGP ladder joined to the roster for the two things
the ladder does not carry, a raider's class and when they last received an
item, beside a personal rail (priority, EP/GP, attendance, last item won).
That rail IS the old Me tab; nothing was lost by the tab going away. A
ladder name with no roster row is still shown, uncoloured, rather than
dropped -- a raider missing from a standings table is the silent omission
this project keeps writing rules about, and a main swap produces it.
ORDERING, one ladder for every mode: Targeted -> BIS (Overall -> Raid ->
M+) -> Major -> Moderate -> Minor -> Sidegrade -> Not an upgrade -> Not
for you. TARGETS PIN TO THE TOP REGARDLESS OF USABILITY -- a target is an
actively chosen thing, and a Resto Druid may legitimately chase Feral
gear. Ties break by upgrade size then name, so the column is stable
between refreshes; a selector whose rows move under the pointer gets
misclicked in the only sixty seconds anyone is looking at it.
THE OPENING LIST FOLLOWS WHERE YOU STAND: inside a raid the question is
what dropped, and an empty drops list is still information there because
something is going to arrive. Anywhere else it is what CAN drop.
TWO CODE GAPS CLOSED because the layout depended on them. The drops list
reads the RECORDER, not the in-memory roll list -- the memory list is
wiped by a /reload and never learns a winner, since the roll event fires
when the window opens and who won arrives minutes later on a rescan.
And ranks are JOINT ON TIES (1, 2, 2, 4), computed over the whole list:
deriving a place from the previous VISIBLE row restarts the numbering at
each scroll page and a tie straddling the boundary comes apart.
BOSS ART IS BUNDLED, 56x56, KEYED BY BLIZZARD ENCOUNTER ID. Two wrong
answers preceded it: the journal's creature icon is WIDE, so a square tile
squashed every face, and asking the client for a proper portrait fixed the
aspect and came back ROUND, because that call renders the circular
unit-frame portrait. The design's tiles are the files Gloom's Build Barn
already ships, keyed there by WCL id -- a different number space -- so
they are renamed at bundle time and the strip is a plain texture lookup.
It also ends a real cost: resolving portraits drove the live Adventure
Guide nine times per refresh, and the icons visibly streamed in.
NO BLIZZARD FRAME TEMPLATE. The inset border kept painting through three
attempts to hide its regions. The template supplied a close button and a
title; dragging was already hand-wired, so a plain frame ends the whole
category of "some hidden region is still drawing" instead of hiding one
more of them. The header is now one image, crest and wordmark together --
which also settles the one thing a fontstring could not do, since the
wordmark is painted with the site's brand GRADIENT and SetGradient does
not exist on a FontString.
A PILL'S FILL IS OPAQUE AND ITS LEVEL IS SetAlpha, NEVER BOTH. Baking the
alpha into the colour as well makes the two multiply, and all four filter
pills read as selected. Written down in Build Barn for the same reason.
Tabs dim their label with the fill and filters do not: an inactive tab is
a place you are not, an inactive filter is a choice still on offer.
Harness: smoke 299 -> 392 checks, covering the ordering ladder and its
target pin, the ordinal's 11/12/13 case, the slot line, the header counts,
today's drops and the winner lookup, the badge ramp, the ladder/roster
join and the default-list rule. Every one was revert-checked against the
code it guards. New guard: the three WINDOW files -- which no harness
loads, so they ship having never run -- are parsed for every ns.Thing()
they call and each name checked against the namespace, which catches the
likeliest fault in an untested file without pretending to test drawing.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
v2026.08.27-0239
Loot Advisor
v2026.08.27-0239 (2026-08-27)
Full Changelog Previous Releases
- Six defects found by the first two-client test
THE RUNNER COMPUTES, EVERYONE DISPLAYS -- settled in Data Contract 4 and
never implemented. handlers.DROPS stored the runner's ranking, refreshed
the panel, and the panel then called RankRaiders and recomputed the whole
thing locally; Comms.AuthoritativeRanking had no callers at all. Clients
hear different subsets of GEAR self-reports, so two panels could order one
drop differently in front of the raid. Loot.RankingFor is now the one
display path -- panel, chat post and roll summary -- preferring the
received ranking and falling back to local for a Boss-tab item or the
seconds before a broadcast lands. A client that never imported a roster
now shows and posts the runner's list instead of "no raid night imported".
The wire carries what a row DISPLAYS: the fourth field was
result.ilvl_delta, the F1 SCORE COMPONENT, not the "+26 ilvl" on screen --
rendering it would have put a raw score under an item level's label
(Core 7.7). It is the ilvl gain now, alongside class, grade/bis and the
usable/total counts. Fields are append-only, so no protocol bump.
GEAR SELF-REPORTING NEVER STARTED. The only trigger was
PLAYER_EQUIPMENT_CHANGED, so a client that logged in already dressed --
every client, every raid night -- reported nothing until it happened to
swap a piece. Live: "Reporting live: 0 of 17" with two installers in the
group, the runner's own row among the missing, and the roster scanner
inspecting people whose own client could have answered for free.
Comms.AnnounceGear now fires on joining a group and on entering the world.
A HELLO IS NOW ANSWERED. It was fire-and-forget, so a client only learned
about people who announced AFTER it started listening -- and both moments
that matter fall outside that window. At group formation the inviter's
HELLO goes out before the joiner is on the channel and HELLO_INTERVAL
blocks the retry; after a /reload the returning client has an empty peer
table and nobody has reason to speak again. Two clients each held the
other's roster while one reported "nobody else has announced" all evening.
The reply is addressed to the sender, only for new peers, once a minute.
AN LFR IS RAID-SIZED AND STILL NOT A "RAID" CHANNEL. resolveChannel had no
instance branch, so IsInRaid() sent Post to RAID and the client refused it
with "You are not in a raid group" -- the button doing nothing, in the one
content type always available to test in. Blamed on comms twice; comms had
picked INSTANCE_CHAT correctly and this had not. SAY is honoured as asked.
THE GUILD-RUN COUNTS WERE ALWAYS NIL. RunnerReport read them through
select(2, fn and fn()), which collapses the call to a single value, so the
panel said "0 of 0 here are guildmates" beside a group it had just
correctly called a guild run. The gate was right; the sentence was not.
AND THE CHANNEL DECISION THE INSTRUMENTATION EXISTS FOR WAS THE ONLY ONE
NEVER LOGGED. The instance branch returned above the logging block, so a
live LFG group reported INSTANCE_CHAT twice through /la comms while the
SavedVariables log held zero of them. "Get evidence first" needs the
evidence to exist. One exit now, and loggedChannel tracks it.
Harnesses: comms 139 -> 171 checks, smoke 296 -> 299, roster 95 -> 97.
Every new check was revert-checked against the code it guards; the roll
summary was rewritten only after a revert proved nothing covered it.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
v2026.08.27
Loot Advisor
v2026.08.27 (2026-08-27)
Full Changelog Previous Releases
- Runner tab, an explicit runner claim, and per-spec trinket grades
WHO RUNS LOOT IS NOW A DECISION, NOT A SIDE EFFECT. "Runner" meant
"whoever pasted", and receiving a roster called SetRunner(false)
unconditionally -- so two people importing at once left ZERO runners,
every later WANT went unanswered, and both clients still showed a full
correct roster. No symptom at either end.
Split into two questions that were tangled:
* WHOSE DATA -- the export's own stamp decides. Older ignored, newer
adopted, identical settled by name so exactly one client answers.
Previously arrival order won, per client independently.
* WHO RANKS -- a deliberate claim (RUNNER message type, Run Loot
Tonight toggle). Survives a newer payload; pasting still claims it
when nobody else has. Re-announced on joining a group, since a claim
made solo reached nobody.
Runner tab (Experience 3): who is running loot, gear-report coverage,
peers and versions, and the spec discrepancies that were computed and
displayed nowhere. Its data lives in Comms.RunnerReport() -- no harness
loads a window file, so logic in Panel.lua ships never having run.
Auto-post drops to chat, off by default, gated on ns.IsGuildRun():
UnitIsInMyGuild counted per person, majority required, failing closed.
Formation-based tests cannot tell a guild raid from a pug raid joined by
invite. This is the only thing the addon writes to real chat.
Ranked rows now carry the OTHER spec's grade when a raider is seen
playing something other than the roster's spec. The ranking does not
move -- roster spec still scores -- but three of six graded Warlock
trinkets this tier disagree between Aff and Destro, one of them
Moderate-vs-Sidegrade. Silent when the grade does not differ.
Also from live testing: duplicate Import/Loot Log buttons removed (they
already exist), the tab now appears on import without reopening the
window (repaint moved into Payload.Store/Clear), the paste box accepts a
click anywhere, "Clear" renamed "Clear Box", and a real Discard Loaded
Data with a confirm.
Harnesses: comms 104 -> 139 checks, roster 84 -> 95. Every new guard was
written to fail first and then revert-checked.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com