Skip to content

Releases: HandofDevastation/HoDLootAdvisor

v2026.08.29-1626

Choose a tag to compare

@github-actions github-actions released this 29 Aug 16:26

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

Choose a tag to compare

@github-actions github-actions released this 29 Aug 16:14

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.
    1. 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.
    2. 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.
  • 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...
Read more

v2026.08.29-1420

Choose a tag to compare

@github-actions github-actions released this 29 Aug 14:20

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...
Read more

v2026.08.29

Choose a tag to compare

@github-actions github-actions released this 29 Aug 16:25

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

Choose a tag to compare

@github-actions github-actions released this 28 Aug 04:27

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

Choose a tag to compare

@github-actions github-actions released this 28 Aug 03:42

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

Choose a tag to compare

@github-actions github-actions released this 28 Aug 00:36

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

Choose a tag to compare

@github-actions github-actions released this 28 Aug 00:26

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

Choose a tag to compare

@github-actions github-actions released this 27 Aug 02:41

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

Choose a tag to compare

@github-actions github-actions released this 27 Aug 01: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