Dibs

A master‑loot tool for Classic Era raids: everyone calls dibs on the gear they want before the raid, so when an item drops the masterlooter instantly sees who's competing — for fast, fair, drama‑free distribution. Works for pugs too.
Back to Files

Dibs-v0.8.0

File nameDibs-Dibs-v0.8.0.zip
Uploader
PmptastyPmptasty
Uploaded
Oct 16, 2026
Downloads
21
Size
2.0 MB
Flavors
Classic TBCClassicForever
File ID
9209183
Type
R
Release
Supported game versions
  • 2.5.6
  • 1.60.1
  • 1.15.9

What's new

Changelog

[v0.8.0] -- loot council, a bench, and EPGP ready to replace your old addon

Changed -- release notes for v0.8.0

Release prep, 2026-10-16. A minor bump, not the patch the v0.7.2 heading assumed: the section carries new player-facing features (loot council, the bench, EPGP rosters, token prices, undo and re-award, per-boss and on-time / completion / interval EP, the imports, Shaman tank). GUI/Intro.lua's UPDATES now lists v0.8.0 only (eight New, one Changed, five Fixed, then whatsnew_v080_known); the whatsnew_v071_* keys are deleted from Locale/enUS.lua. The CurseForge page gains the v0.8.0 block, matching bullet for bullet, and drops v0.5.1 (five versions kept). Its feature list gains a Loot council section, the EPGP section gains per-boss and bonus EP, token prices, undo / re-award, rosters and the imports, and the Raids section gains the bench and the disposal rows; the README's EPGP, Raids and new Loot council rows say the same. Both known-issues texts add loot council and the bench to the still-being-tested list and say the imports have not been run against real saved data. The README no longer links docs/AUDIT.md, which was deleted on 2026-10-09. docs/24-test-run-checklist.md names a v0.8.0 key as its example, and the docs that dated EPGP's earn routes "v0.7.2" now say v0.8.0. This entry took the file 601 characters over the 120,000 ceiling, so the rest of v0.5.2 moved to CHANGELOG_ARCHIVE.md.

Release prep first paused the token default-price work (todo td28996257ef4ea87); the operator put it back into v0.8.0, so it was built after prep and the notes were rewritten to match (see the token-prices entry below). The boss-kill bullet now names the 4 EP default and the token bullet lists quivers as unpriced (both Peer Review, thread ca080f16). The bench bullet's first draft said officers could add people to the bench; the UI only lets them remove (raid_bench_manage), so it was reworded before it shipped.

New -- Shaman tank, the fourth Shaman spec

Operator, 2026-10-14: "add shaman tank spec as the 4th spec for shamans, so we can get the right EP list for them".

  • Data/ItemSources.lua SPEC_MAP[7] gains tank, so it reaches Items.CLASS_SPECS.SHAMAN (declarations, soft-reserve restrictions). Detection walks talent tabs 1..3 only, so nobody is detected as a tank; the player picks it.
  • The weights come from ItemDB (v1.5.0, inbox thread 46c74258): its stock "hawsjon" scale now ships [7].tank on Vanilla, TBC and Forever, a labelled copy of paladin protection, since no published Classic shaman tank scale exists. ItemDB also confirmed Classic Era shamans can parry (Enhancement talent "Parry", spell 16268), so the scale's parry weight stands. Shamans get it as an ordinary "Tank (default)" weight template, and Items:ScoreForClassSpec scores it like any other stock spec.
  • Dibs' interim stand-in is gone: the block at the top of Modules/Weights.lua that registered a private "tank" scale, DefaultTemplates' add("tank"), ScoreForClassSpec's fallback to that source, the templateLabel "(tank)" branch in GUI/Weights.lua, and the weights_tank_template locale key. A saved source = "tank" (written by a dev build of this version) is folded to the stock scale by one local, live, on both branches of Weights:Active and in SaveCustom, because the library answers nil for an unregistered source and the template would otherwise open empty -- and Save As would copy the dead literal into every new template (Peer Review, thread af33e116 D2).
  • An ItemDB too old for the spec is now said out loud (Peer Review, thread af33e116 D1, correcting my own "Dibs cannot detect it"): ItemDB made no MINOR bump, but GetBiSWeights answers nil for a spec the scale lacks, so the data is the detect. New Weights:MissingScaleSpec names the active default template's spec when the installed scale has no row for it, and the Weights tab shows "Your ItemDB has no Tank weights ... Update ItemDB to v1.5.0 or newer" (weights_scale_missing) beside its scale-provenance note. It covers the player's own active template; another raider's declared shaman tank still scores nothing on an old ItemDB, with no note.
  • SPEC_SCHOOLS' comment no longer calls the shaman tank's scale a stand-in (D3).
  • Modules/SoftRes.lua: shaman tank is a physical-camp spec and is in the built-in Tank restriction template.
  • Corrected two comments that described a shipped "tank" scale of protection/feral_tank specs; those live in "hawsjon". docs/Dibs-Integration.md no longer lists a warden shaman token that is not in the data.
  • Modules/SoftRes.lua CanReserve: tier annotated string?, clearing an editor warning.

New Tests/shamantank_spec.lua (17 tests, against the real LibItemDB): the Vanilla, TBC and Forever scales each carry [7].tank, Dibs registers no "tank" scale of its own, the template seeds from the library's numbers, the retired source is folded on the default branch, the custom branch and Save As, the old-ItemDB note fires with the row removed and stays silent with it present, for a custom template, and with no spec, and it scores. Mutation-checked: disabling the default-branch fold, the SaveCustom fold and the missing-row return each turned its own test red. Not tried in a game client. The numbers are still paladin protection's, not a measured shaman scale.

Tests -- the /dibs handler at full coverage, and three gaps in the specs around it

Peer Review, thread 7f41afea (reply 549a6628). Dibs.lua is at 182/182 lines.

  • Tests/locale_spec.lua's scan also matches ns.GetLocale()["key"] and C.GetLocale()['key'], not only L["key"], so a typo in a direct lookup (the /dibs chat prints among them) no longer prints the raw key name with the spec green. The wider scan found no missing enUS key in the current tree. Red-first confirmed: with the two new patterns removed, only its self-test failed.
  • Every method name the /dibs routing stubs may fake (ROUTED in Tests/bootstrap_spec.lua) is checked to be a real function on the module that ships it, so a renamed method cannot hide behind a stub.
  • The second Dibs.lua load in the no-C_AddOns test unregisters the ADDON_LOADED frame it builds, so a later spec cannot run a second bootstrap over the saved variables.

New -- import Core Loot Manager's EPGP settings

Block 1.7 of docs/26 (Vishiswaz in #dibs, 2026-10-14: CLM "migrates all of your CLM data AND settings", naming "the GP values of items"). /dibs import clmsettings [roster] previews a CLM roster's settings as Dibs' EPGP settings and writes them only on Accept. Officers only. Settings are guild-wide, so a second roster overwrites the first.

  • Live CLM (Import.ReadCLMSettings, through the _G.CLM global CLM publishes): GP scale (multiplier x base tier multiplier / 4.83, EPGPWEB equation at exponent base 2 only), per-slot multipliers as slot overrides, base GP from minGP, each EP award (0 when CLM has it switched off), the interval in minutes, standby EP as floor(boss EP x benchMultiplier), per-boss EP folded across difficulties, and per-item GP.
  • Per-item GP becomes a substitute item level (EPGP.SetTokenPrice), so the price stays inside the one GP formula and moves with the scale. Import.SolveItemLevel solves at gp + 0.5 and checks the answer by pricing it back; it keeps the item level unrounded when two decimals would change the GP. A non-gear item is priced as a chest piece. Import.ApplySettings prices every written item again afterwards and reports any that no longer match.
  • Pasted JSON export (Import.CLMSettingsFromExport): CLM's export carries only the flat config, so it brings base GP and the EP awards. The preview says what it could not read.
  • Reported, not imported: the Wowpedia equation or another exponent base, a zero slot, a boss priced differently on two difficulties, an item priced per bid tier, starting EP/GP, hard and weekly caps, class multipliers, hard-mode boss values.
  • New Import.SETTING_ORDER, ImportUI.SETTING_LABEL_KEY, and locale keys import_settings_*, import_set_*, import_why_* and import_usage_settings. The roster picker chooseRoster is now shared by the standings import and the settings import.

Peer Review's findings on the first version (thread c3b921b6), fixed here:

  • A DKP roster could be imported as EPGP settings. The pasted export was never checked, and the live path checked only when CLM's constants carried a name I had guessed. CLM's constants are now read from its source (POINT_TYPE = { DKP = 0, EPGP = 1 }, SLOT_VALUE_TIER.BASE = "b", ITEM_VALUE_EQUATION.EPGPWEB = 1) and kept as fallbacks (Import.CLM_*). The export's type field is compared with them. A roster whose type cannot be read still previews, with a note asking the officer to check.
  • minGP = 0 became a base GP of 0, which leaves every raider with no GP without a priority (EPGP.PR answers nil). A minGP below 1 is now a note, not a write. Settings.MIN.epgpBaseGP = 1 refuses it from a hand edit or a push too. New Settings.MigrateBelowFloor, run from Dibs.lua at ADDON_LOADED beside MigrateEarnMode, clears a value saved below its floor before that floor existed, so the default (1) applies, and prints one chat line naming the change (settings_below_floor). It does not move the settings stamp. KNOWN COST: an officer on an older Dibs who still pushes a base of 0 has that one key refused by newer clients (ApplySystem writes through Settings.Set), so the guild disagrees on base GP until that officer updates; the load-time repair on their own client, once updated, ends it. It also applies to a GP scale saved below its floor, which goes back to its default. Three tests in Tests/bootstrap_spec.lua (one holds the chat line's labels to Settings.MIN) and one in Tests/clmsettings_spec.lua, mutation-checked.
  • A failed read of a loaded CLM said CLM was not loaded and opened the paste box. It now answers read_failed with the error text.
  • The setting keys were listed twice with nothing checking they agreed. A spec now holds the GUI's label table to the module's list.
  • Removed an unreachable return "" from settingsSubject in GUI/Import.lua.

New Tests/clmsettings_spec.lua (53 tests). Sixteen production lines were broken one at a time across two rounds, each turned its test red, and each was restored. One test sends an unrounded token item level through the real AceSerializer and EPGP.AdoptTokenPrices and checks the receiving client charges the same GP. Modules/Import.lua and GUI/Import.lua are at 100% line coverage. Not verified: CLM's data shapes come from reading its source, not from running it (CLM is not installed here). That CLM's boss ids are the game's encounter ids is assumed. Standby EP is approximate once a guild has per-boss values. An absent autoAwardIncludeBench is assumed to mean the bench is paid. Nothing was tried in a game client.

New -- loot council voting, per item

Block 2.1 of docs/26, designed in docs/27, under directive 09b5a6f0: loot council is a per-item decision, not a loot method, and a council item costs no points. The master looter sends one row of the loot table to the council ("Send to loot council", first in the row menu, pending rows only, never on a live table). Raiders answer it Main spec, Off spec or Bid like any row and can change their answer while it is open; council members vote on the board; the master looter awards from it.

  • Model (Modules/LootCouncil.lua, new): ParseList, QualifyList, IsMember, Context, AmMember, Candidates, Vote, Prune, VotersFor, Tally, VoteOf, VotePayload. The master looter is always a member; guild officers are members while lootCouncilOfficers is on; the rest come from the typed lootCouncil list.
  • Session (Modules/LootSession.lua): a council mode that takes no /rolls and treats a raider's answer as interest. In an EPGP raid a council row is the council's, not EPGP's, in both AddChoice and Resolve.
  • Loot table and master looter (GUI/LootTable.lua, GUI/Masterlooter.lua): new ML:CouncilTableRow, cloned from AwardTableRow; UI:CouncilRow, UI:VoteCouncil, UI:OnCouncilVote; a Vote button and a votes column on the board (shown to council members only, the leader in gold for the master looter, the voters in the button's tooltip, a second click withdraws). A council award skips the SR+ reset, the static price and the EPGP charge, and its winner line reads "by the loot council". A soft-reserve loot-council hold row now opens for answers instead of staying closed.
  • Sync (Modules/LootTableSync.lua): the board payload carries council and votes; a new loot_council_vote message that only the raid's master looter acts on.
  • Settings: lootCouncil (default empty) and lootCouncilOfficers (default on), officer-owned and synced, on the Rolls tab beneath Distribute Later. B:Text takes an optional normalizer so the list saves through QualifyList. 24 new locale strings.
  • Peer Review fixes (threads 565dc454 and 28ba50a0): VotersFor matched names by exact spelling and came back empty for a differently written name; a typed name without a realm now gets the officer's realm on save, so a same-named stranger from a connected realm cannot vote; a raider's answer on a council row must be Main spec, Off spec or Bid, so a modified client cannot put free text on every member's board; and the master looter's client runs a vote's sender through Names.Full before the membership check, so "Ana" from another realm is not counted as the council's "Ana-RealmB".
  • Removed: council as a loot-method input mode in Modules/LootMethods.lua, with its spec and the docs/10 lines that offered it, per the same directive.

New Tests/lootcouncil_spec.lua and Tests/mlcouncil_spec.lua (18 tests); every production line they name was broken, went red and was restored. Tests/srhold_spec.lua and Tests/lootmethods_spec.lua pinned the old closed council row and now pin the new shape; Tests/settingsui_spec.lua counts the new toggle and text box. Not tried in a game client: board layout, button overlap and column widths are unverified.

New -- EPGP rosters for raid groups running at once, and alts that share their main's points

Asked on Discord, as Core Loot Manager has both. Design in docs/28-epgp-rosters.md.

  • A roster is its own ledger pair. Main is the existing epgp_ep/epgp_gp, so no data migrates and a guild with no rosters sees no change. Another roster is epgp_ep@<id>/epgp_gp@<id> with its own sync types. Not a roster field on each entry: an older client drops unknown fields and relays the entry without it, so two current clients could file one entry under different rosters. The id comes from the name (EPGP.RosterId), so two officers creating "Tuesday" get one roster. The list (DibsDB.epgpRosters) rides the settings push as epgpRosters; removing a roster deletes no points.
  • A raid's roster is pointsRoster on its record (Modules/Raid.lua), absent meaning Main, set only by the raid's starter (RaidController:Start(raidKey, roster), MoveToRoster); a move back to Main is written as an explicit "main" (that alone did not stop an older relay undoing a move -- see the pointsRosterAt fix below). Standings, awards, charges and adjustments default to the roster of the raid this client belongs to, else Main, so the loot window charges the right pool with no change of its own. Undo and re-award find the charge in whichever roster holds it. Decay with no roster decays every roster. Economy settings are shared by all rosters.
  • poolAltPoints (officer-owned, off by default): every shown or spendable EPGP and DKP number is the account's total across linked alts (DKP.PooledBalanceOf, DisplayBalances, pooled EPGP.Standings). Entries still name the character who earned or won, so turning it off restores each balance exactly. (This line first said decay, import and the zero-sum split read per-character balances and that decay costs a pooled account at most a point per character. That was wrong for DKP and too loose for trust; see the self-audit fix below.)
  • DKP gets pooling, not rosters: its bank, round-robin cursor, seasons and auctions would each need the roster threaded through, and the request was for EPGP. Filed in docs/08-open-questions.md with three more: per-roster economy settings, moving a raid's existing entries when its roster changes, and decay's default once a picker exists.

New Tests/epgprosters_spec.lua (38 tests), ten mutations each caught. Modules/EPGP.lua, Modules/DKP.lua and Modules/RaidController.lua stay at 100% line coverage.

Where it shows (GUI/Settings.lua EPGPSettingsUI.DrawRosters, GUI/Tracker.lua TrackerUI.RosterPicker): the EPGP tab has a roster picker plus New / Rename / Remove (guild rank only), and the picked roster drives that tab's standings, Adjust, Undo and Run decay, so Run decay now decays only the picked roster. The pooling checkbox sits under the EPGP master toggle and stays live with EPGP off, since it governs DKP too. The Live raid pane has a roster picker under Start/End, hidden while Main is the only roster; picking on an open raid you started moves it. The Tracker's DKP column reads DKP.DisplayBalances, so it pools like EPGP's. /dibs import has no window, so it imports into the roster picked on the EPGP tab, and its preview names that roster once there is more than Main. Specs: Tests/epgpsettingsui_spec.lua (11), Tests/trackerraid_spec.lua (10), Tests/tracker_spec.lua (1), Tests/import_spec.lua (4), each mutation-checked; Tests/epgpundo_spec.lua's non-officer test was typing into the new roster box and passing for the wrong reason, and now indexes the right box. Not tried in a game client, and live roster sync between real clients is unverified.

New -- officer-set answer buttons and a raider note on loot council rows

Block 2.2 of docs/26. An officer types up to four answers (twelve characters each) into lootCouncilResponses, e.g. "BiS, Upgrade, Minor"; rows the soft-reserve setup flags "Loot council" then offer those plus Pass. Empty, they keep Main spec / Off spec / Pass.

  • The wire carries a token (r1..r4), never the label; the labels travel with the row and every reader sanitises them. LootSession AddChoice's council branch takes Main / Off spec / Bid plus r1..rN for the N answers that row offered, through LootCouncil.IsChoice, and nothing else.
  • The note is capped at 80 characters and stripped of colour, texture, atlas, link and newline escapes, every | and any split UTF-8 character -- on the raider's client, again when the master looter stores it, and again when a board draws it; dropped if the sanitiser is not loaded. Council members see it in a Note column and tooltip; raiders do not.
  • Model (Modules/LootCouncil.lua): Sanitize, SanitizeNote, ParseResponses, Responses, RowChoices, ResponseIndex, IsChoice, ChoiceLabel; Candidates ranks configured answers first and carries the note. Modules/LootTable.lua rows carry responses and Select/Adopt take a note; Modules/LootTableSync.lua sends the note sanitised and the board carries it on council boards only. GUI/LootTable.lua draws the buttons, a Note button and its popup (UI:SetNote, UI:PromptNote).
  • Setting lootCouncilResponses, officer-owned and pushed with lootCouncil, on the Rolls tab.
  • The council vote now fails closed (Peer Review thread 28fba7ce): UI:OnCouncilVote refuses a vote when Names.Full is missing or returns nothing, instead of falling back to the bare sender and reopening the cross-realm hole fixed above.

New tests in Tests/lootcouncil_spec.lua (15), Tests/mlcouncil_spec.lua (8) and Tests/srhold_spec.lua (2), each production line mutated red and restored; Tests/settingsui_spec.lua counts the new text box. Not every Sanitize strip was mutated, only the texture one. Not tried in a game client.

New -- why an item went to the disenchanter or the bank is in the loot log

Block 2.3 of docs/26. Disenchant and Bank are a new loot-log kind, a disposal (LootLog.RecordDisposal): the recipient goes in its own to field, never in winner, so nobody's soft-reserve plus is reset and nobody's points move. winner holds a placeholder no character can be named ("@disenchant", "@bank"), because every client's Accept, older ones included, refuses a row with no winner and sync would re-offer it forever. The SR flag is forced false in RecordDisposal, Record and Accept, and every reader skips the kind by name: WonSRItemIn, SRWinnersIn, SRWinnersInRaid, WonBefore, EPGP's charge matching, Forfeit/Reattribute and the Tracker CSV. On an older client a disposal lands as a non-SR row won by "@disenchant", which nothing there can credit or charge to a real character.

  • Recorded from disposeToEnchanter (GUI/Masterlooter.lua recordDisposal), so every disenchant path writes one: the row button, the DKP no-bid outcome, the disposal roll and an owed item. Also from the no-bid Bank branch.
  • A loot council award carries reason = "free": a real win that charged nothing.
  • The Loot Log tab reads "Disenchanted (Ana)", "Guild bank (Mel)" or "Bob (free)", Type "Not won".

New tests in Tests/lootlog_spec.lua (9), Tests/disenchant_spec.lua (2 plus a bank assertion) and Tests/mlcouncil_spec.lua (1), all mutation-checked. Not built: officer-defined reasons, a Free hand-out outside the loot council, and a reason on a held "Guild bank" row handed out by hand.

Fixed -- disenchanting an owed item still counted it as the absent winner's

ML:RerollOwed forfeits the first winner's loot-log row once the new roll finds a winner; ML:DisenchantOwed did not. So when a Distribute Later winner who had left had their item sent to shards, they still read as having "won it before". DisenchantOwed now forfeits the same row after the disenchant goes through, and not when it is refused. Pinned in Tests/distributelaterml_spec.lua; removing the forfeit turns it red.

Corrected (self-audit 1bccec03, finding 6): this entry and the comment in ML:DisenchantOwed first said the forfeit also fixed an SR winner's plus "staying reset". It does not. The SR+ readers (WonSRItemIn, SRWinnersIn) ignore forfeits by design (Modules/LootLog.lua Forfeit's note: re-crediting a forfeiting player is a decision nobody has made), so an SR winner's plus stays reset either way. Only the "won it before" half is fixed.

Fixed -- shared alt points trusted any self-declaration, and paid or charged per character

Self-audit 1bccec03, findings 2, 3, 4, 7, 8 and 10, with Peer Review's rulings on the thread.

  • Pooling only trusts an officer's link (finding 4). New Declaration.LinkSnapshot (the one link walk, shared with MainOf), ConfirmedMain and PoolMainOf. A bare self-declaration no longer pools: saving a declaration locks it, so a lock filtered nothing, and a pug who declared an officer its main could spend the officer's account and push its GP into the officer's PR. Nothing in the declaration model lets a main confirm an alt (a lock's by is self-asserted; same-account proof exists only on the owner's client), so an officer link record is the only trusted source. If the owner saves a declaration after the link, it must name the same main. The DKP pool (DKP.LivePool), the EPGP PR fold and dkpAltSpend (SpendableOf / ChargeFor, via DKP.PoolResolver) all use it. Behaviour change: under alt-spend an alt reaches its main's points only once an officer links it. The account views still group by declarations, where a false claim only mislabels a row.
  • The earning dedupe uses the same rule, pooling on or off (Peer Review's re-review). DKP.LiveOncePerAccount (shared by EPGP's boss award) and DKP.LiveStandby resolved accounts through the bare Declaration.MainOf, and the dedupe keeps one character per account, so a pug who declared an officer its main and was seen later took the officer's kill, and a benched officer earned no standby while that pug raided. Both now take PoolMainOf. Cost: an unlinked alt raiding while its main is benched earns both, as two unrelated characters would, until an officer links them. Two cases in Tests/poolwriters_spec.lua, red against the old lines (run 20261014-092410.665-1048316769). The DKP.SpendPool comment now names PoolMainOf; docs/28 records the import and season-leaver decisions and that a link record is trusted, not unforgeable.
  • The declaration lock uses it too (Peer Review thread 60a3198d). Declaration.AccountKeyOf and AccountChars resolved the lock's account through the bare MainOf, so a pug who saved "my main is Bob" minted a LOCK on Bob's account (re-locking Bob after every officer unlock), an unlock approved for the pug released Bob, and pressing Unlock for Bob approved the pug's pending request. Both now take PoolMainOf. Cost: an unlinked own alt locks and unlocks as its own account; an old lock record filed under its main no longer reaches it, so it falls back to its own saved locked flag and stays locked. Three cases in Tests/officerlink_spec.lua red against the old lines (run 20261014-093840.206-3822737171) plus one pinning that an officer-linked alt still shares the lock; three Tests/declunlock_spec.lua cases that linked the alt by bare claim now link it by officer record.
  • Under pooling the account is the unit for every writer whose amount depends on a balance or is paid per member (findings 2 and 3); the comment claiming the fold "never reaches what is written" is rewritten. DKP decay charges each account floor(total * pct / 100), spread over its positive characters by the deterministic DKP.Spread, and skips an account at or below zero (a +300 main with a -200 alt lost 30 of a 100 account). EPGP decay keeps exactly floor(|total| * keep) per account (EPGP.PooledDecayDeltas) and now writes in sorted order. The starting balance is paid once per account (Newcomers takes mainOf); a season reset brings the main to N and linked characters to 0 (PooledResetAmounts); the absence penalty is once per account and none if the account attended on another character (AccountAbsentees). DecayPlan / ResetPlan feed both the writers and the DKP tab's Season preview, so the count an officer confirms is the count written. Pooling off: unchanged.
  • Run decay names the roster it decays, in its tooltip and confirm (finding 7).
  • Remove roster removes an empty roster (finding 8): EPGP.KnownRosters counts a ledger only when it holds an entry (ledger tables are created on first read); a roster a raid ran on stays.
  • The DKP tab pools (finding 10): per-character rows read DKP.DisplayBalances, and the group-alts view groups by the pool's links, so a raider sees the same figure as the Standings tab and bid popup.

New Tests/poolwriters_spec.lua (36 cases, one per writer under pooling plus the trust, decay, reset, start and roster cases): 14 red against the unfixed code, then two mutation rounds with every targeted case red. Alt-spend fixtures in dkp_spec, dkpauction_spec, dkpbid_spec and epgprosters_spec moved to officer link records. Known and left: a character imported under pooling keeps the imported figure each, and an archived alt cleared at season end takes its own points out of the shared account.

Fixed -- giving a shard debt to the disenchanter cancelled an unrelated win

Self-audit 1bccec03, finding 5. The trade-timer menu offered "give to disenchanter" on an enchanter's own shard debt (reason == "de"); ML:DisenchantOwed then forfeited LootLog.PriorWin(enchanter, item) -- that enchanter's newest real win of the item, possibly from another week -- and logged a second Disenchant row. TradeTimer.ChoiceRows no longer offers it and ML:DisenchantOwed refuses a shard debt before disposing anything. The re-roll path in ResolveRoll (activeReroll) had the same forfeit and now skips it for a shard debt. Three new specs in Tests/distributelaterml_spec.lua and Tests/tradetimer_spec.lua, each red against the unfixed code.

Fixed -- a raid's EPGP roster could stick on the wrong roster behind an older relay

Self-audit 1bccec03, finding 1. A build without pointsRoster relayed the author's move to Main as a newer envelope with no roster; a client holding "tuesday" kept it, and the author's real copy at the same updatedAt could only fill an absent field. Raid.EditDigest left out Main, so the copies never converged, and a master looter on that client paid raid start/end EP into the wrong roster. The record now carries pointsRosterAt, the roster's own clock, set by Raid.New and Raid.SetPointsRoster; Raid.Accept merges the roster later-stamp-wins in both branches (mergeRoster) whatever updatedAt says, an absent field loses to any value, and two values with one stamp break the tie on the larger id. The digest folds in the raw value and its stamp, explicit "main" included; Raid.Wire carries the stamp. Four specs in Tests/raid_spec.lua ("a roster move survives an older relay"); three were red against the old code, the fourth went red when the stamp comparison was flipped.

Fixed -- the main/alt link import could fold an alt into a third person's account

Self-audit 1bccec03, finding 9. classifyLink (Modules/Import.lua) never checked whether the import's main was itself an alt in Dibs: Guild Roster Manager saying A belongs to M while Dibs had M under X wrote A to M, putting A on X's account, and only M's own row was flagged. It also overwrote a character that had declared itself its own main. Both are now reported as a conflict (MAIN_ELSEWHERE, SELF_MAIN, with their own preview text) and never written; the preview and the accept-time re-check use the same classifier. The Tests/importalts_spec.lua case that asserted the alt "still links" pinned the bug and now asserts the conflict; three new cases, each red on the old code.

Tests -- raid controller line coverage to 100%

Four tests at the end of Tests/raidcontroller_spec.lua drive the last seven uncovered lines of Modules/RaidController.lua: a party leader on their own roster, two open raids started in the same second, UnitKey with no name normaliser, and PLAYER_LOGIN registering the raid sync and marking a stored raid handled. Each mutated red and restored; no code changed. Modules/EPGP.lua, Modules/DKP.lua and Modules/RaidController.lua are at 100% line coverage.

New -- a re-awarded EPGP charge moves its Loot Log row

The re-award below used to move the GP and leave the Loot Log naming the first winner. Now an item charge is stamped with the Loot Log row it pays for (loot, outside the canon, carried by sync; LootLog.JustRecorded finds the row the award site wrote just before the charge), and EPGP.ReawardCharge re-attributes that row through LootLog.Reattribute (the Distribute Later forfeit plus a new row for the new winner), then stamps the new charge with the new row so a second re-award follows it. An older charge with no stamp falls back to LootLog.FindAward (same winner and raid, closest in time), which refuses to choose between two equally close rows; with no row the GP moves alone and nothing is written to the log. New LootLog.RowByCanon.

Fixed -- two master looters undoing one charge refunded it twice

EPGP.UndoCharge and DKP.UndoAward refuse a second undo only against entries this client already holds. Two clients that each believed they held the master looter role could reverse the same charge before either synced; both reversals survived the merge and the winner was refunded twice. A concurrent re-award also charged two raiders for one item. (Peer Review G2, thread 5771ab04.)

The fix is a read-time rule, because the authoring guard cannot see an entry that has not arrived: Ledger.FirstUndoWins keeps the earliest reversal of each target (then by author, then key), the same pick on every client in any arrival order. The ledger takes a discount function (Ledger.New), and BalanceOf, AllBalances, the new Effective and History all leave the losers out; nothing is deleted. Bound:UncountedCanons memoises per store and entry count. EPGP.Discounted also drops a losing re-award's new charge (the charge written straight after its reversal, same author and moment) and any reversal of a charge that does not count. DKP.Discounted groups a reversal by author and moment, since one UndoAward writes one entry per raider.

Also fixed while writing this entry: EPGP's reversal test now requires a loot entry (KIND_RAID), as DKP's always did. An officer's Adjust takes a free-text reason, and one reading undo:<canon> used to mark that charge undone, so the master looter's real Undo was refused and the GP stayed charged.

New Tests/concurrentundo_spec.lua (19 tests), written red first and mutation-checked; the officer-note test went red with the kind check removed (run 20261014-045357.246-3538300151).

New -- import main/alt links from Guild Roster Manager or Ultimate Guild Roster

Asked by Vishiswaz on Discord (thread c43e1dd4). /dibs import grm and /dibs import ugr read the alt groups of whichever of the two is installed and link each alt to its main as an officer's link (Declaration.SetLinkAsOfficer), the same record the Accounts tab's "Points go to" correction writes. Neither addon is a dependency. GRM's saved GRM_Alts table is read directly, because its public GetMemberAlts returns alts without the main; UGR's UGR.Alts:Listed() view is read rather than its saved variables, because the view resolves overlapping groups.

Conflicts are reported, never overwritten: an alt is linked only when Dibs has it on no account. A row is shown and left alone when Dibs already has the alt on a different account, when other characters are linked to it, or when the source's group has no main. New in Modules/Import.lua: AltGroupsFromGRM, ReadGRMAlts, ReadUGRAlts, FromAltGroups, PreviewLinks and the apply; GUI/Import.lua previews the plan. New Tests/importalts_spec.lua.

Fixed -- a test that only failed when another ran first

Tests/epgpsettingsui_spec.lua found the officer-adjustment boxes by counting every box above them. After Tests/epgp_spec.lua ran, the item library it had loaded was still in place, the EPGP tab drew one box per raid boss, and the count picked a boss box for the character. Now epgp_spec unloads what it loaded in a teardown, and the boxes are found by their tooltip titles instead of their position (Peer Review R1 and R2, thread e8f2b057). The first adjustment test also prints the panel's refusal line when it fails.

New -- EPGP undo and re-award of an item charge

The master looter can now give back the Gear Points for a won item, or move them to whoever really got it (docs/26 block 1.6, DKP's UndoAward for EPGP; Core Loot Manager's EPGP plugin reverses GP on a re-award). An EPGP charge is one GP entry whose reason is the bare item, so unlike a DKP award group it is keyed by its canon. New in Modules/EPGP.lua: UndoReason(canon) (undo:<canon>), UndoneSet(entries), UndoableCharges(limit) (item charges not yet undone, newest first), UndoCharge(canon, by, at) and ReawardCharge(canon, newSubject, by, at).

  • Undo writes one loot entry (KIND_RAID, like the charge itself) of minus the charge, under the charge's raid, and refuses a second undo (already_undone), an unknown canon (not_found) and anything that is not an item charge (not_charge). Nothing is deleted; the history shows the charge and its return.
  • Re-award is that undo plus an ordinary item charge of the same GP to the new character, under the same raid, so the new charge can itself be undone or moved. Same GP, not a re-price: the officer's settings may have moved since the drop. Refuses a blank name (no_char) and the current holder (same_char).
  • Only the master looter may undo or re-award (not_ml), through DKP.MayAuthor, the gate DKP.UndoAward uses. I first shipped this ungated and writing an officer entry, arguing that whoever sees the tab can already Adjust. Peer Review's G1 (thread 5771ab04) showed that was wrong: the tab is drawn for CanManageLoot, which admits a loot leader with no guild rank, and every other client's LedgerSync.Authorizer refuses an officer entry whose author has none. So a non-officer ML's undo stayed on their own client while a re-award's new charge synced, charging two raiders for one item everywhere else. New specs run the entries through the real authorizer as a receiver with a non-officer ML as author.
  • The same hole in Adjust and Decay, closed in the tab. Both write officer entries behind the same CanManageLoot tab, so a loot leader without guild rank wrote corrections only their own client kept. Both buttons now refuse without Comms:IsOfficer, saying why (epgp_adjust_err_not_officer). Tests/epgpsettingsui_spec.lua's officer-action block now states its officer stub instead of inheriting a CanManageLoot-only one from an earlier block.
  • An undo refunds what was charged, not what is left after a decay -- DKP's undo does the same. A decay between the charge and the undo leaves the member slightly below where they would have been; Adjust corrects it.
  • Tab: under the EPGP tab's officer actions, after Adjust and Decay, EPGPSettingsUI.DrawChargeUndo: an "Item charge" menu of the last 15 undoable charges (ChargeMenuItems, pure; each row names the raider, the GP and the time of the charge, because the charge carries no item and two wins at one price would otherwise read the same), a "Re-award to" box resolved through the Adjust rows' ResolveSubject, and Undo charge / Re-award buttons, each behind a confirm that names the charge. The pick is cleared after a success. Height counts the four new rows.
  • Not built at first: re-attributing the Loot Log row on a re-award. Built since; see "a re-awarded EPGP charge moves its Loot Log row" above.

The tab refuses a non-master-looter's Undo or Re-award BEFORE its confirm (EPGP.MayUndo, exposed for that), rather than after they have said yes; found in my self-audit of the G1 fix, each half mutation-checked (runs 20261014-041430.703-2741560458 and 20261014-041438.078-1098259568).

New Tests/epgpundo_spec.lua (20 tests); Tests/epgptokens_spec.lua's box count adds EPGPSettingsUI.UNDO_BOXES. Mutation run 20261014-035617.429-4095109104: the second-undo guard, the re-award's new charge, clearing the pick and Undo's nothing-picked refusal each turned their tests red (6 failed). The G1 fix's mutation runs: the reversal back to KIND_OFFICER, no ML gate and no Decay officer gate turned 4 red (20261014-040906.413-2961309219); no Adjust officer gate turned 1 red (20261014-040926.166-1467939027). Restored, whole suite 6278 passed, 0 failed (run 20261014-041642.684-3243597427). Not verified in a game client.

Also: ML.EPGPPrice's doc comment now says a tier token is unpriced only until an officer prices it (Peer Review nit on thread fec7242e).

New -- EPGP token prices

An officer can now price a tier token (docs/26 block 1.5, the design in docs/10): a substitute item level and slot per item id, never a GP, in DibsDB.epgpTokenPrices = { [itemID] = { ilvl, slot } }. New EPGP.TokenPrice, TokenPrices, SetTokenPrice (a blank item level clears; a slot outside SLOT_KEYS is refused), SerializeTokenPrices, AdoptTokenPrices and PriceItem(itemID, item), which swaps in the substitute and calls GPForItem, so the officer's scale and per-slot override price a token exactly as they price the gear it buys. The token's own rarity is kept. ML.EPGPPrice now calls PriceItem, so the charge and the award board both use it.

  • Push: epgpTokenPrices rides Settings.SerializePush as its own field, absent-means-silent and replace-on-adopt, like epgpSlotModifiers.
  • Tab: "5. Token prices" on the EPGP tab, a fixed-shape block so saving a token does not grow the tab: a menu of priced tokens (picking one loads it), a Token box registered with ItemInteract.AcceptLinks so a shift-click lands in it, an Item level box, a Slot dropdown and Save. Save reads what the boxes show, so a shift-clicked link is saved without pressing Enter.
  • Also covers ammo, shirts and tabards: a token price is keyed by item id and does not look at the item's slot, so it is how an officer charges for a particular one of those after G2 below made them unpriced.
  • A default price from the gear a token turns into (Vishiswaz, Discord thread c61fc512; built 2026-10-16). Filed as an ItemDB contract (thread 1c5f8e8d) rather than read from a third addon, and delivered as DB:GetTokenRewards(itemID) at LibItemDB MINOR 44 (ItemDB v1.5.0). New EPGP.TokenDefault(itemID) returns the item level and slot of the highest-item-level reward the formula can price by slot (a tie keeps the lowest id, so every client agrees); PriceItem tries the officer's price, then this default, then the item's own. It feature-detects GetTokenRewards, so an older ItemDB leaves the token unpriced as before. ItemDB's comment on GetTokenRewards names a GetSlot it does not have; Dibs uses its own Items:GetSlot and Items:GetItemLevel. The settings note, the cleared-price message and the release notes now say a token is priced by default.

New Tests/epgptokens_spec.lua (19 tests) and one in Tests/mlepgp_spec.lua: Desecrated Robe through the real LibItemDB, priced at item level 76 chest, costs 36 (worked by hand: 4.83 x 2^(76/26) = 36.63); 73 with the chest modifier at 2; 18 at rarity 3. Tests/epgpsettingsui_spec.lua's officer-action box lookup now counts EPGPSettingsUI.TOKEN_BOXES. Mutation run 20261014-034454.701-260682055: PriceItem ignoring the token price, Save not reading the boxes, the push dropping the field and ApplyPush not adopting it each turned their tests red (8 failed); restored, whole suite 6258 passed, 0 failed (run 20261014-034723.760-4272512720). Not verified in a game client, including whether a shift-click reaches the Token box.

The default (2026-10-16) adds five tests to Tests/mlepgp_spec.lua, which now loads ItemDB's real TokenRewards.lua and Armor.lua: Desecrated Robe defaults to its item-level-92 robes and costs 56 (worked by hand: 4.83 x 2^(92/26) = 56.12); Qiraji Bindings of Command takes its top reward's slot, item-level-81 shoulders; a non-token has no default; an ItemDB with no GetTokenRewards leaves the token unpriced without raising; and an officer price still wins, with clearing it falling back to 56. Breaking the fallback in PriceItem and flipping the highest-item-level comparison turned exactly their tests red; restored, whole suite 6744 passed, 0 failed (run 20261016-111728.402-2352414945). Not verified in a game client.

Found in the self-audit: the Core Loot Manager settings import (Modules/Import.lua, the item rows of PreviewSettings) re-priced an item with the officer's token price and GPForItem, a second pricing path that skipped the new default. A token whose default already cost CLM's GP read as unpriced and the import wrote a redundant officer price, which would then pin it against a later ItemDB correction. It now asks EPGP.PriceItem, the charge's own pricing. New test in Tests/clmsettings_spec.lua; putting the old line back turned it and the idempotency test red (run 20261016-114028.101-731234700); restored, whole suite 6745 passed, 0 failed (run 20261016-114445.104-2243266022). ChargeEPGP's doc comment no longer calls an unpriced item "the tier-token case".

New -- EPGP per-boss EP

An officer can now give each boss its own EP, as Core Loot Manager does (docs/26 block 1.4). DKP's per-boss store became a factory, DKP.EncounterStore(field, defaultKey), returning ValueFor / Override / Set / Serialize / Adopt; DKP.BossValueFor, BossOverride, SetBossValue, SerializeBossValues and AdoptBossValues are that store over dkpBossValues, unchanged in behaviour. EPGP keeps its own table, DibsDB.epgpBossValues, through the same factory (EPGP.BossEPFor, BossOverride, SetBossEP, SerializeBossEP, AdoptBossEP). A separate table rather than DKP's, because DKP points and EP are different currencies and a guild switching systems must not inherit one as the other.

  • Payout: EPGP.OnEncounterEnd(success, opts, encounterID) pays EPGP.BossEPFor(encounterID); DKP's ENCOUNTER_END frame now passes the id through.
  • Push: epgpBossValues rides Settings.SerializePush as its own field, absent-means-silent and replace-on-adopt, like dkpBossValues.
  • Tab: "4. EP per boss, by boss" on the EPGP tab, the DKP tab's raid-by-raid list. The row loop and its height are now one local pair (drawEncounterRows, encounterRowsHeight) shared by both tabs. Officer actions renumbered to 5.
  • Ceiling: EPGP.CeilingFor takes bossEP, and TightestCeiling a bossEPOf(profile), so the warning sums the officer's per-boss values (EPGP.RaidBossEP) instead of bosses x epPerBoss.

Specs in Tests/epgp_spec.lua drive the real DKP.EncounterStore: a kill pays its boss's value and an unset boss pays epgpEpPerBoss, the EP and DKP tables stay apart, clear / serialize / replace-on-adopt, and the absent-store fallback, plus RaidBossEP and both ceiling routes. Passing nil for encounterID at the payout and pointing the store at dkpBossValues each turned its own spec red. Tests/nocaller_spec.lua's pooled-export ceiling moved 108 to 110: EPGPSettingsUI.DrawBossValues shares its short name with the DKP tab's, and both are called by their own tab's Draw. Not verified in a game client.

Fixed -- an officer's dead raid record that once held tonight's leader no longer takes over tonight's raid

Peer Review (thread 726baa9a, item 1): both of RaidController.DefersToOther's exemptions read my record's roster, which only grows, so a record from a group that fell apart earlier in the evening, which happened to include tonight's leader, did not defer to the leader's raid, out-overlapped it at formation, and Snapshot wrote tonight's group into it. New seenBefore(at, r): when the other record's author is my group's leader, my record listing them counts only if it first saw them (present, never overwritten) at or after that record's startedAt. A legacy true stamp keeps the roster-only behaviour. Cost: startedAt is the other client's clock and the stamp is mine, so clock skew can flip a close call; not measured. Specs (7) and (8) in Tests/bench_spec.lua.

Peer Review (thread 583c2e14, finding 8): each of seenBefore's two call sites was then disabled on its own and both turned red. New spec (9) in Tests/bench_spec.lua: a dead record that lists tonight's leader only through an officer's amend made before the leader's raid began still defers. The finding assumed an amend could stamp 0; it cannot, because Raid.Amend places its stamp after the record's updatedAt. Spec (10) then went red on the case Peer Review asked for (reply ad8163e5): an amend made after the leader's raid began read as "first seen after the start", so the dead record stopped deferring. An amend is an officer's edit, not a sighting. New sightedBefore(own, name, r) judges the leader from present only, and a leader listed by amend alone counts as seen before, so the record defers.

Fixed -- a tier token was charged as body armour in an EPGP raid

Peer Review (thread 726baa9a, T1) found that my Discord answer to Vishiswaz, docs/10, docs/26 and the comment on ML.EPGPPrice all said a tier token is reported unpriced and charged nothing, and the code did not do that. A token carries an item level (Desecrated Robe, 22351: quality 4, INVTYPE_NON_EQUIP_IGNORE, item level 60), GPForItem returned nil only for a missing item level, and an unlisted slot fell to DEFAULT_SLOT_MODIFIER 1, so the token was charged about 23 GP at the default scale with nobody told. New EPGP.UNPRICED_SLOTS (INVTYPE_NON_EQUIP, INVTYPE_NON_EQUIP_IGNORE, ""): GPForItem returns nil for a named non-equippable slot, so the master looter is told and nothing is charged, until the substitute-item-level override (docs/26 block 1.5) lands. A nil equipLoc is still priced as body armour, and an explicit slotModifier (the settings tab's hypothetical) is always priced. Specs in Tests/mlepgp_spec.lua: a non-equip item with an item level is not charged and the master looter is told, an ilvl-60 chest still costs 23, and 22351 through the installed LibItemDB prices nil from ML.EPGPPrice. Deleting the check turned the first and third red.

Peer Review (thread 99a5140b, G1) then found a second "not gear" list, Items:IsGear (nil, "", INVTYPE_NON_EQUIP, INVTYPE_BAG), disagreeing with UNPRICED_SLOTS both ways: a token was gear there and a bag was gear here. IsGear had no caller in production or the specs (it was a filed delete candidate in Tests/nocaller_spec.lua), so it is deleted. A bag and a quiver (INVTYPE_BAG, INVTYPE_QUIVER) are now unpriced in EPGP; a relic stays priced. UNPRICED_SLOTS is NOT Dibs' only such list, as this entry first said: DKP prices by SLOT_GROUPS. A spec in Tests/epgp_spec.lua walks every not-gear slot through GPForItem; it went red on INVTYPE_BAG before the change.

G2 on the same thread: EPGP still priced ammo, shirts and tabards through DEFAULT_SLOT_MODIFIER 1 -- ammo at a chest's price, with no SLOT_KEYS row for an officer to change it -- while DKP's SLOT_GROUPS left all three unpriced, and the spec only asked ammo for a number. INVTYPE_AMMO, INVTYPE_BODY and INVTYPE_TABARD are now in UNPRICED_SLOTS, taking DKP's answer (a consumable stack and two cosmetics). The epgp spec now asserts the gear slots' values (ilvl-60 epic chest 23, relic 11, unknown slot 23), and a new Tests/dkp_spec.lua test walks every paperdoll token plus INVTYPE_BAG through DKP.SlotGroupOf and EPGP.GPForItem and asserts both leave exactly the same ones unpriced. Both went red before the change (run 20261014-032759.311-3169983188); removing INVTYPE_AMMO again turned both red (run 20261014-032836.653-2094162470).

G3 (thread 44f06193) said the Planner's Quiver slot could never list a candidate, because its only invtype is INVTYPE_QUIVER and LibItemDB files a Vanilla quiver as INVTYPE_BAG. Traced, it does list them: itemsForSlot searches by item class 11 and returns before either equip-location branch. Nothing pinned that, so Tests/plannerdraw_spec.lua now draws the Planner over the real LibItemDB quiver file, clicks the Quiver slot and asserts quiver 2101 is listed; deleting the class-11 query turned it red with 0 rows. The Constants.lua QuiverSlot comment now says INVTYPE_QUIVER is a marker there, not a match.

Fixed -- an EPGP raid now earns EP for every boss kill

EPGP.AwardBoss had no production caller. Every spec called it and nothing in the shipped addon did, so a live EPGP raid earned Effort Points only when an officer typed an adjustment on the EPGP tab -- while that tab offered "EP per boss", the economy-ceiling warning priced raids from it, the CurseForge page said raiders "earn Effort Points for turning up", and the comment at GUI/Settings.lua:1659 reasoned from EP arriving that way. My defect: I built and specced the function and never wired it. Found by the RCLootCouncil / Core Loot Manager comparison (docs/26-rclc-clm-comparison.md), not by a test.

New EPGP.OnEncounterEnd(success, opts) pays epgpEpPerBoss on a kill. It applies the same gates as DKP's boss award, read from the same places: a wipe pays nothing, the raid must be running EPGP (RaidSystems.Active("epgp")), only the master looter authors (DKP.MayAuthor), and only members inside the instance earn (DKP.GroupRoster({ inInstance = true })). It rides DKP's existing ENCOUNTER_END frame rather than registering a second one, since Modules/EPGP.lua holds no frames, and DKP and EPGP are never both on for one raid. Six specs in Tests/epgp_spec.lua cover the handler. One spec in Tests/dkp_spec.lua fires the real event frame and checks that EPGP receives Blizzard's success slot. The wipe, EPGP-on and master-looter gates and the event wiring were each mutated and went red. A separate zero-EP gate stayed green under mutation, because AwardBoss already refuses zero, and was removed. Not verified in a game client.

Peer Review (thread 6957fd9c, Finding A) caught that the first version skipped DKP's once-per-account rule, so a raider boxing a declared alt earned every kill on both characters, and that its docstring claimed the two systems "cannot disagree" while they did. The account fold is now one function, DKP.LiveOncePerAccount, called by both DKP's earning() and EPGP.OnEncounterEnd; a seventh spec pins a main and declared alt earning once. The pug filter (dkpPugSupport) is deliberately still DKP-only, since EPGP charges a pug GP for what they win, and the docstring now says so instead of claiming parity.

New -- EPGP on-time, completion and interval EP

The rest of DKP's raid-level earn axis, for EPGP (docs/26 block 1.1, asked for by Vishiswaz in #dibs). Four new officer settings on the EPGP tab, all in Settings.SYSTEM_KEYS: epgpOnTimeEP, epgpCompletionEP, epgpIntervalEP (each 0 by default) and epgpIntervalMins (30). Zero defaults are deliberate: the economy ceiling's shipped numbers were tuned against boss EP alone, so a guild that touches nothing earns exactly what it did before.

  • EPGP.AwardEP(subjects, amount, reason, by, at, raid) is the shared body; AwardBoss now calls it.
  • EPGP.OnRaidStart / EPGP.OnRaidEnd are called from RaidController.Opened / RaidController.Settle beside DKP's, with the record's last-seen stamps. EPGP.IntervalTick and EPGP.StartIntervalTicker / StopIntervalTicker mirror DKP's ticker, armed and stopped from DKP's group-event frame.
  • Every route goes through one local earnersFor in Modules/EPGP.lua: EPGP on for the raid, master looter only, once per account. The interval tick also refuses with no raid key or while the raid timer is paused, for DKP.IntervalTick's reasons.
  • Reasons are DKP's own spellings (ontime, raid, interval) so the history surface's existing labels (GUI/Tracker.lua:1064) cover them with no new locale keys.
  • Settings.OnCommitted re-arms EPGP's ticker when either interval setting changes while grouped. Unlike DKP's, EPGP's ticker arms on its amount, so the amount change has to start or stop it too.
  • The ceiling warning (EPGPSettingsUI.CurrentCeilingLine) now counts on-time plus completion as the per-raid bonus EPGP.CeilingFor already accepted. Interval EP is left out because its total depends on raid length, and its tooltip says so. The Settings.lua comment claimed this before it was true; it is now.
  • Standby EP came with the bench list; see "New -- the bench" below.

Twelve specs in Tests/epgp_spec.lua. Mutation-checked: the interval tick's no-raid and paused gates and the ticker's cancel-on-rearm each went red when deleted. The reason pins first compared against the constants and stayed green when REASON_ONTIME was mutated; they now assert the literal spellings. The RaidController call sites and the OnCommitted re-arm are specced by the next section. Not verified in a game client.

docs/26 also gains a verified comparison of CLM's default GP equation, read from CLM's source: its default EPGPWEB equation is Dibs' formula at scale 0.1, with six slot multipliers that differ. Multiple point rosters moved from "Not proposed" to block 3 after a player asked for them.

Fixed -- an unidentified raid's awards follow the guild default, and DKP's on-time bonus reads the right record

Two defects Peer Review found in the self-audit of the section above (inbox thread 47f99ffe); the second was DKP's own and older than EPGP's earn routes.

  • An unidentified night (Raid.UNKNOWN) was gated by whatever instance the leader stood in. RaidController.Opened / Settle turned ? into a nil key meaning "guild default", but DKP.OnRaidStart, DKP.OnRaidEnd, DKP.ApplyTimeFloor and EPGP's earnersFor all resolved a nil key through CurrentRaid(). Settle runs wherever the leader is -- after a hearth, or inside the next raid's instance -- so that instance's per-raid toggle decided whether the night paid its completion bonus and time floor. DKP.OnRaidEnd's own comment promised the guild default and the code did not deliver it. Now the record hooks pass unkeyed = true, and both systems ask the new RaidSystems.ActiveFor(system, raidKey, unkeyed), which answers GuildActive when unkeyed and Active otherwise. A direct call with no key still means "the raid I am in", which Tests/concurrentraids_spec.lua pins.
  • DKP's on-time bonus folded alts by the wrong record's stamps. DKP.AwardOnTime called earning with no stamps, so the once-per-account fold read RaidController.Live(), which a record arriving through sync need not be; with no live record it fell back to alphabetical order. EPGP's on-time award was handed the opened record's stamps, so the two systems could pay different characters of one account. RaidController.Opened now builds one options table, stamps included, for both, and DKP.OnRaidStart forwards present.

Specs: Tests/attendancecommit_spec.lua captures EPGP beside DKP and pins unkeyed, the key and the shared stamps at both hooks; Tests/concurrentraids_spec.lua ends and starts an unkeyed night inside BWL (DKP on there, off guild-wide) with a control call each; Tests/dkp_spec.lua pays the on-time bonus by handed stamps with no live record; Tests/epgp_spec.lua covers the unkeyed gate; Tests/intervalrearm_spec.lua pins EPGP's re-arm on both keys, the grouped start, and that neither system's interval key re-arms the other; and Tests/epgpsettingsui_spec.lua checks the four new EP keys are officer-pushed. Mutation-checked: each of the five production changes was undone and its own spec went red.

Peer Review's answer to the self-audit of that fix (thread 182a22f9) asked for four more changes, all made:

  • A nil or blank raid key was still not unkeyed. Opened / Settle only treated Raid.UNKNOWN as unkeyed, so a record with a nil key passed nil with no unkeyed, and DKP.OnRaidStart fell straight back to CurrentRaid() -- the bug above, by another door. New Raid.AwardKey(r) is the one spelling: nil, "" and UNKNOWN all answer (nil, true). Both hooks use it. Start and Accept never write such a record, so this needs a corrupt or foreign copy; specs drive Opened and Settle directly with both shapes.
  • EPGP's earnersFor guarded ActiveFor and DKP's hooks did not, so a missing function made EPGP pay nothing silently while DKP raised. The guard is gone; both raise.
  • The file-wide duplicate-set-field disables in Tests/attendancecommit_spec.lua and Tests/intervalrearm_spec.lua are narrowed: the first routes its thirteen re-points through one standIn helper carrying a single disable-next-line, the second marks each of its four lines.
  • DKP.Enabled now says in its docstring that a record-keyed route asks ActiveFor with Raid.AwardKey.

New -- the bench (standby list), with standby EP and DKP

docs/26 block 1.2, asked for by Vishiswaz in #dibs. docs/25 designed the bench on a scheduled night, and that scheduling moved to ClassicCalendar, so the bench lives on the live raid record instead.

  • Raid.SetBench(r, player, on, by, at) writes r.bench[player] = { on, by, at } -- amend's shape and merge, newest entry per player, a union across copies. It is on the wire (Raid.Wire), merged on both branches of Raid.Accept (the same-envelope branch reports "merged"), in Raid.SyncStamp and in Raid.EditDigest (a b: part). Raid.Bench(r) lists it in sign-up order; being benched never makes anyone an attendee.
  • Who may write an entry: the player themselves, or leadership. RaidSync.FilterEdits keeps a bench entry only when its author is the player (Raid.SamePlayer, the one spelling the sender's RaidController:SetBench / AmBenched use too) or passes the officer-edit test. RaidController:SetBench(player, on) applies the same rule before writing; player nil is the self sign-up. Raid records sync on the guild leg as well, so a guildmate outside the group can sign up (not verified in a client). KNOWN COST: the entry's by is sender-supplied and records are relayed by anyone, so a modified client can bench another player by naming them as author (signing was withdrawn, directive c414a757). The online test below bounds what that earns, and the victim can take themselves off. A non-officer raid leader's removal is judged by live group state, so a guildmate outside the group drops it and keeps the name on their bench view -- the same cost the amend caveat carries. Both are written at RaidSync.FilterEdits.
  • Standby points: new officer settings epgpStandbyEP and dkpStandbyValue, 0 by default, both in Settings.SYSTEM_KEYS and on their tabs. At a kill, after the boss award and behind its gates, EPGP.OnEncounterEnd and DKP.OnEncounterEnd pay the live bench through DKP.LiveStandby / DKP.StandbyOnly, which keeps only benched players the guild roster shows online at the kill (LibGuildRoster IsOnline, false for a non-member; no roster pays nobody), drops anyone whose account already has a character in the raid, and pays the rest once per account. The online test runs before the account pick, so an account is paid through its online character. Without it a raider who signed up and logged off earned every kill of the night, and an undeclared alt left benched while its main raided earned each kill twice (Peer Review B1, thread 182a22f9; docs/25 "Bench credit" already said "online during the raid"). EPGP files these under the new EPGP.REASON_STANDBY ("standby", DKP's spelling via the new DKP.AwardStandby), and the DKP history labels it "Standby" (dkp_reason_standby). DKP.AwardStandby applies only the pug rule (EarningRaiders): StandbyOnly already folded once per account, and the second fold earning ran used the live record's stamps and could pick a different survivor (thread 6c8ff0cc item 4). The ceiling warning leaves standby EP out: it pays people who are not in the raid.
  • Roster still loading: IsOnline reads LibGuildRoster's roster table, which is not complete right after the payer's login or reload (IsRosterComplete false), so every benched player read offline and the kill paid the bench nothing, silently. DKP.LiveStandby now checks IsRosterComplete first: when incomplete it still pays nobody, but prints one line (standby_roster_incomplete) naming the system and who would have been considered (online status unknown, so offline sign-ups are listed too and the line says so), so an officer can award whoever was online by hand. No retry and no timer. A lib without IsRosterComplete is trusted as complete (thread a6d3d0a6 item 1). The line printed on every client at first, because DKP.OnEncounterEnd runs everywhere and the payer check sat inside AwardStandby, after the print: a raider who had reloaded was told the bench went unpaid while the master looter paid it. The standby block is now gated on mayAuthor (Peer Review N1, thread 31005301); EPGP's already returned earlier.
  • Two guild raids at once (thread 6c8ff0cc item 1). Every open record reaches every online guildmate on the guild leg, and "the live raid" was simply the newest open record in the store -- so once group A's raid had synced to group B's leader, B's Start was refused outright, and a kill paid whichever bench was newest. My defect: Raid.Live's comment said two open records "should never" exist, and for a guild that was never true. Now RaidController.Belongs(r) ranks each open record for this client (3 its author is in my group, my own record included when I am grouped; 2 I started it and am solo; 1 I am on its roster; 0 somebody else's; a stale record that ranks at all drops to 0.5), and the new pure Raid.LiveFor(store, rank) picks the highest, newest on a tie. RaidController.Live() is that pick, falling back to the newest open record so a guildmate outside every group still sees a raid; the new RaidController.Mine() is the pick only when it ranks above 0. Everything that pays, stamps or holds reads Mine: the standby payout (RaidController.MyBench, read by both OnEncounterEnds), the redistribution recipients (Attendees), the loot log's raid canon, both interval ticks' pause check, and Pause / Resume, which carry no author check of their own. Start refuses only on an open record that is mine or my group's (RaidController.Blocks), stale or not. RaidController:SetBench(player, on, canon) and Bench / AmBenched take an optional canon; BenchedOn() finds the raid I am benched on, and BenchNeedsPick() is true with more than one raid open and none of them mine -- then Join opens a menu of the open raids (TrackerUI.BenchRaidItems, new key raid_bench_pick), and the strip shows and leaves whichever raid I am benched on. Raid.Live is removed; its spec callers use LiveFor. Callers stubbed with only Live / Bench still work, because each payer reads Mine or Live and MyBench or Bench.
  • Four more holes in that fix, found by Peer Review and my own audit of it (threads 6c8ff0cc N2, 6ee9dcc8).
    • My own forgotten raid outranked tonight's (N2). The first ranking put "I started it" at 3 above "its author is in my group" at 2, so an officer who had left a raid open and was master looter in somebody else's raid had Mine() on the old record: their client paid the old record's bench and named the old night's raiders for redistribution. Being grouped now ranks first and startedAt breaks the tie, and a stale record (IsStale) that ranks at all drops to 0.5, below every fresh one. Start's refusal moved off the rank to Blocks, so a forgotten raid of my own still stops me starting another, as before. That first pass left a hole Peer Review found (N2b): grouped, I am always in my own group, so every record I authored ranked 3 and a NEWER dead one of mine -- started for a group that fell apart, before I joined tonight's raid -- still won on startedAt. Inside rank 3 the tie now breaks first on how many of my current group are on the record's roster (a fraction under 0.5 added to the rank). Ranking my own records below other authors' was rejected: it would let a guest officer's open record outrank the raid I lead. The overlap alone still lost the race at raid formation (N2c): a leader who invites the master looter first gives a 1-1 tie, my newer dead record won on startedAt, and one Snapshot wrote the leader into it, after which it kept the night. New RaidController.DefersToOther() -- another author's fresh open raid has its author in my group and I do not lead it (UnitIsGroupLeader("player")) -- drops my own records to rank 2 in Belongs, so Live() and the Snapshot that writes to it are tonight's. The leader guard keeps the raid I lead above a guest officer's, and a record whose roster already lists my group's leader never defers: my own audit found that a raid an assistant started (Start admits assistants, who do not lead) dropped below a guest officer's record the moment the guest joined, and stopped recording for the night. Deference is now also PER RECORD (N2d): my record defers only to an author its roster does not already list. Two officers in one group, neither leading, each with an open record listing the other, used to defer to each other's record so neither client wrote and the two picked different live raids; now neither defers and both pick the same one. Peer Review's other N2d case (I hand lead to an officer carrying a dead record of their own) was already held by the roster-lists-the-leader exemption above, and is now held by both rules.
    • A solo bench player was benched on a forgotten raid (N5, thread 2851efff). A stale record kept 0.5 for any rank, including "I am on its roster", so a raider who attended Monday's never-ended raid and pressed Join on Thursday got Monday's record as Mine(), was benched there, and Thursday's master looter never paid them. A stale record I merely attended now ranks 0; 0.5 stays for my own or my group's, which is still mine to End. MyBench's other-raid filter likewise skips stale records, so a forgotten raid's roster does not block tonight's bench pay (self-audit item A).
    • A raider could be paid by both raids (N3). With two raids open, a raider in A who benched on B earned A's kills as a raider and B's as standby, because StandbyOnly only knows the paying raid's kill roster. RaidController.MyBench now drops anyone present on another open record (the guild leg delivers every open record, so that roster is on the payer's client); presence is grow-only, so someone who left A and benched on B stays unpaid by B for the night. And SetBench refuses a self sign-up (in_other_raid, said in chat as raid_bench_in_other_raid) when I belong to a different open raid, or am in a raid group with no open record yet -- the leader who has not pressed Start. Leaving is never refused.
    • End from outside every group closed the newest open raid (item 8). RaidController:End(canon) takes the raid; with no canon, EndNeedsPick() (more than one open, none mine) refuses with needs_pick, and the End button opens a menu of the open raids under the heading raid_end_pick, then confirms the one picked. The left-the-group prompt ends the raid it named, by canon.
    • OpenRaids sorted the whole archive on every repaint (item 1). It walked All(), which sorts every raid ever held, and the bench strip calls it on each Live repaint. It now walks the store and sorts only the open records. inMyGroup and Snapshot also share one group walk (groupMembers) instead of two copies (item 6).
  • UI: a strip under the Raids tab's Live roster -- "Join the bench" / "Leave the bench", the benched names in order, and for officers and raid leaders a "Remove..." menu. TrackerUI.BenchStrip and TrackerUI.BenchMenuItems are its pure halves.

Specs: new Tests/bench_spec.lua (model, wire / merge / stamp / digest, the receipt filter, the controller's authority, the real StandbyOnly, Raid.AwardKey); standby cases in Tests/epgp_spec.lua and Tests/dkp_spec.lua (the dkp ones through the real account filter); the strip's pure halves in Tests/trackerlive_spec.lua; offline-at-the-kill, online-character-wins and no-roster cases in bench_spec and dkp_spec; the "Standby" label in Tests/tracker_spec.lua; roster-still-loading cases in dkp_spec, and an EPGP offline-benched case driven through DKP's real LiveStandby in epgp_spec (thread a6d3d0a6 item 2). Mutation-checked: six production breaks, each red by its own test, then restored; removing the online test turned its specs red, EPGP's new case included; and removing the roster-complete gate turned the roster-loading case red. The two-raids fix has a two-leader block in bench_spec (both Starts succeed, a second Start inside one group is refused, each group sees its own raid as live, each master looter's MyBench is only its own raid's, an outsider is asked which raid and benched on the one picked), Raid.LiveFor's ranking in Tests/raid_spec.lua, and a "pays the master looter's own raid's bench" case in both dkp_spec and epgp_spec; N1 has a non-master-looter no-print case in dkp_spec. Mutation-checked: the old any-open-record Start test, Live ignoring the rank, MyBench reading Live, and the N1 gate removed each went red by its own test. The MyBench one is caught only by the client-in-neither-raid case: for a master looter inside a group, Live and Mine pick the same record, so the in-group payout case stays green under that break. The four follow-up fixes have eight more cases in that block: tonight's raid beats the master looter's own older open one (Mine, Live, MyBench, Attendees, and their Start still refused) and their own newer dead one, the raid I lead beats a guest officer's newer record, a forgotten record ranks below a fresh raid I was on, B's bench does not pay a player present on A, a grouped raider's Join on another raid is refused, End asks an officer in neither raid and ends only the one named, and OpenRaids lists only open raids without calling Raid.All. Mutation-checked in two batches, each break aimed at a different case: authorship ranked above the group, the stale demotion, MyBench's other-raid filter, the self-bench refusal, End's pick guard, and OpenRaids on All() -- each turned exactly its own case red and nothing else; dropping the roster-overlap tiebreak turned exactly the two N2b cases red. N2c and N5 add six more: a 1-1 formation tie and a dead record holding more of my group both leave tonight's raid as Mine() and Snapshot writes nothing into the dead one; the leader's Snapshot writes the raid they lead and not a guest's; a solo player on a forgotten raid's roster is benched on tonight's raid, or asked to pick between two fresh ones; and a forgotten raid I started is still mine to End. Mutation-checked: disabling the deferral turned exactly the two N2c cases red, removing the leader guard exactly the guest case, and restoring 0.5 for a roster-only stale record plus dropping MyBench's stale skip turned exactly the two N5 cases and the forgotten-roster bench case red. N2d adds three: after I hand lead to an officer carrying a dead record, my raid stays Mine() and my Snapshot still writes it; the new leader's client picks the same raid and writes nothing; and two non-leading officers whose records list each other pick the same live raid. Mutation-checked: removing the per-record clause turned only the neither-leads case red; removing the leader exemption turned only the assistant case red; removing both turned those two and the handed-lead case red. Not verified in a game client, and the Live strip's frames (both picker menus included) are not specced.

New -- import standings from CEPGP, EPGP-Classic and Core Loot Manager

docs/26 block 1.3, asked for by Vishiswaz in #dibs: a guild switching to Dibs no longer starts its economy from zero. New Modules/Import.lua (parsers, a preview, an apply step; pure, everything the client supplies is injectable) and GUI/Import.lua (/dibs import cepgp | epgp | clm [roster] | clmdkp [roster], wrapping the existing /dibs handler at load so Dibs.lua is unchanged), both added to all three TOCs at the same position. What each source stores was read from its source on GitHub and is cited in the module header.

  • EPGP-Classic: officer note EP,GP, decoded with its own DecodeNote pattern. Its stored GP already excludes base GP, so it is imported as written. A non-numeric note is that alt's main's name; the alt is skipped and the preview names the main.
  • CEPGP: officer note EP,GP (the retail fork's optional third field is dropped). Its GP includes the base, so the import subtracts it. Every raider keeps exactly their priority when Dibs's base GP is set to the old addon's first, and the preview says so.
  • Core Loot Manager: CLM keeps no officer notes, and its SavedVariables are only readable while CLM is loaded, which would make Dibs depend on the addon it replaces. Its JSON standings export is read instead, decoded with C_EncodingUtil.DeserializeJSON (present in the Era, Classic and Forever API trees): EP is points, GP is spent minus the base; for a DKP roster points is the balance. CSV is unsupported by CLM itself and XML has no client decoder.
  • How it writes: each balance is SET to the source value by writing the difference from the current one, recalculated at write time, through EPGP.Adjust (officer kind) and DKP.OfficerAdjust -- so it is ledgered and synced like a hand-typed correction, and a second run writes nothing. Guild officers only (Roles.Can(..., "dkp.adjustAny")): peers reject officer entries from anyone else. Each row is shown as one of write, already matches, blank, malformed, alt, conflicting duplicate (neither copy written), unknown name or ambiguous name.
  • Tests/import_spec.lua, 45 cases. Twelve production lines mutated, each red by its own case; one (the can-read-officer-notes gate) was green at first because a second guard refused too, and the case was fixed so only that gate can refuse. EPGP.StandingOf left Tests/nocaller_spec.lua's allow-list, since the import calls it.

Not verified in a game client: the officer-note reads, the confirmation popup, and the JSON decoder on a real CLM export (1-based arrays are assumed), and whether CLM's player names carry a realm. The import is reached only from the slash command for now. EPGP.Adjust broadcasts once per entry, so a 40-member import sends up to 80 broadcasts (not measured).

Fixed -- stale EPGP lines in the design docs

docs/05-settings.md's tab table listed "EPGP / Council -- Placeholders; not built" and had no Wishlist Loot row; docs/08-open-questions.md said EPGP had "no surface yet" and was a "placeholder tab". All three now describe the built EPGP tab, and docs/05 states there is no loot council tab because council is a per-item hold. README.md's EPGP row said Effort Points were "for turning up", the same overclaim the CurseForge page had; it now says "for every boss killed", which was the only EP trigger built at the time (the section above adds the optional rest).

docs/08-open-questions.md's EPGP entry also said EP triggers "reuse the DKP earn axis" and that resolve was "highest PR with ties to a /roll"; EPGP has no roll and only the boss kill is wired, and it now says so (Peer Review, thread 065f2b77). docs/10-loot-methods.md described the tier-token item-level override as built when Modules/EPGP.lua has only per-slot overrides, and said EP per boss reused DKP's per-boss values when it is one flat setting; both are now marked designed-not-built with their docs/26 todo. docs/10 also gains how EP is earned, and a stated difference from DKP: EPGP has no dkpAltSpend-style alt pool, so the two match under DKP's default and diverge only for a guild that turns alt spending on.

Added -- docs/26: Dibs against RCLootCouncil and Core Loot Manager

Vishiswaz, #dibs 2026-10-13, asked for Core Loot Manager's EPGP features and RCLootCouncil integration. The operator reframed it the same day: "the user wants to replace RCLootCouncil and Core Loot Manager with Dibs, so they want us to do a feature comparisson and see where we currently stack up, and what we need to develop further." docs/26-rclc-clm-comparison.md is that comparison: a feature table across the three addons and a ranked list of gaps. Sources are each addon's own wiki and source, and Dibs' own code.

[v0.7.1] -- random-enchant gear gets its EP

Changed -- release notes for v0.7.1

Release prep, 2026-10-12. A patch bump: everything since Dibs-v0.7.0 is a fix. GUI/Intro.lua's UPDATES now lists v0.7.1 only (whatsnew_v071_suffix under Fixed, and whatsnew_v071_known, v0.7.0's known-issues text unchanged); the whatsnew_v070_* keys are deleted from Locale/enUS.lua. The CurseForge page gains the v0.7.1 block and drops v0.5.0 (five versions kept), and docs/24-test-run-checklist.md names the v0.7.1 key as its example. The player bullet says healer scoring of the spell damage and healing bonuses is not yet checked in game, as the Peer Review entry below requires. README and the page's feature list needed no change: neither claimed random-enchant items were unscored. Self-audit thread d10e58a2: the bullet's first draft named "a ring of the Owl" unchecked; it now names the "of the Owl" and "of the Bear" families, which Peer Review confirmed in ItemDB's suffix table.

Fixed -- random-enchant gear ("of Striking", "of the Owl") shows its EP

Cpthammer88, #dibs 2026-10-12: "dibs does not know what to do with some blues with varied stats ... Abyssal Leather Leggings ... dibs doesn't assign them an EP. Pawn is able to." Loadout.Scorable refused every link with a random suffix, because the suffix's stats are not on the base item id and scoring the id alone answers about a different item. So the tooltip EP line, the upgrade arrow, every "vs. yours" column, the upgrade popup and the per-spec block all said nothing for these drops. That refusal was the narrow fix; this is the complete one that Modules/Declaration.lua's comment already described.

Data/ItemSources.lua gains Items:SuffixStats(link): the client's GetItemStats on the full link minus the same call on that link with field 7 (the suffix) zeroed, so both sides come from one API in one vocabulary. The difference is folded by Items.FoldSchoolStats (an "of Fiery Wrath" school's spell damage counts only for a spec that casts that school, the same gate GetItemScore applies) and scored by the library's ScoreStats. Items:GetEP(id, hand, link) and Items:ScoreForClassSpec(id, classID, spec, { link = }) add it to the base item's score, each on its own weight scale; the base item's set, use and proc value is unchanged. Loadout.Scorable now accepts a suffixed link whose suffix can be read and still refuses one it cannot (an uncached item), failing closed when there is no reader. Loadout:DropDelta and Items:OffHandEPFrom take the link through, and SpecUpgrades.ForItem passes it. Items:SuffixStats memoises a link that answered (never a not-loaded nil), since one hover asks it once per spec.

The WORN side too (self-audit, same day): Loadout:EquippedSlotEP and SpecUpgrades.SlotEPForSpec scored the worn item by id, so a suffixed drop compared against a suffixed piece already worn counted the drop's suffix and not the worn one's, and read as too large an upgrade. Both now pass GetInventoryItemLink, falling back to the base item's score when the worn suffix cannot be read. Specs: Tests/scorable_spec.lua (+2) and Tests/specupgrades_spec.lua (+2), both mutation-checked.

Peer Review on that self-audit (thread 23224f93, reply c9ffb968):

  • Healer credit for "of Sorcery". If the client reports "Damage and Healing Spells" as the single key ITEM_MOD_SPELL_POWER, ItemDB's alias reads it as spell damage only (ItemDB/Scoring/Vanilla.lua:78-80, where the key means aura 13 in ItemDB's own effect data), so a healer got nothing for it. Items.FoldSchoolStats now counts that live key as damage AND healing, which is what the Classic stat does. Which key the client reports is still NOT VERIFIED; if it reports the two halves separately this branch never runs. Release notes must say that healer scoring of random-enchant items is unverified until someone reads GetItemStats on a suffixed link in a Classic Era client.
  • The three field-7 readers are tied together. ML.ItemKey, Declaration.SuffixOf and the zeroing rewrite in Items:SuffixStats each read the suffix field; new Tests/fieldseven_spec.lua runs seven link shapes (plain, positive and negative suffix, blank fields, an enchanted link, a short item string, a bare id) through all three and asserts they agree and that the rewrite changes field 7 only. Mutation-checked by zeroing field 8 instead and by refusing blank fields. The Data file reading a GUI module (ns.MasterlooterUI) is a LOW finding left for a later move of ItemKey into a shared module.
  • The Discord answer overstated that the EP fills in once the item loads; nothing repaints on an item load, so it shows on the next hover. A one-line correction was posted to the player.

NOT CHANGED: the auto Main/Alt tier (Declaration.TierForItem) still withholds its stat axes on a suffixed drop and asks the player. NOT VERIFIED in a client: which stat keys Classic Era's GetItemStats reports for a random enchant's healing and spell damage lines. ItemDB's alias table folds both ITEM_MOD_SPELL_HEALING_DONE spellings, and the per-school damage keys are folded here. Specs: Tests/suffixscore_spec.lua (new, 13, mutation-checked on the GetEP suffix term, the ScoreForClassSpec link term and the zeroed base link), Tests/scorable_spec.lua (+4), and the loot table and roll window column specs retitled to the unreadable-suffix case they still pin.

[v0.7.0] -- resistance sorting, and two group-loot fixes

Changed -- release notes for v0.7.0

Release prep, 2026-10-12. GUI/Intro.lua's UPDATES now lists v0.7.0 only (whatsnew_v070_resist, whatsnew_v070_untick, whatsnew_v070_rollhang, and whatsnew_v070_known rewritten from v0.6.0's with "new" dropped from live rolling); the whatsnew_v060_* keys are deleted from Locale/enUS.lua. KNOWN COST: v0.6.0 shipped only days ago, but Tests/locale_spec.lua pins UPDATES to one New / Changed / Fixed run matching the CurseForge page's newest block, so its bullets cannot be carried; the page still has them. That spec's PAGE_ONLY is now empty, since v0.6.0's LibDBIcon bullet is no longer in the newest block. The CurseForge page gains the v0.7.0 block and drops v0.4.7 (five versions kept), and its feature list and the README now mention the Resistances sort and that unticking "Let Dibs handle loot" opts out of the raid-wide switch. docs/24-test-run-checklist.md names a v0.7.0 key as its example. ItemDB MINOR 43 is ItemDB v1.4.1, which is prepared but NOT yet tagged (its newest tag is ItemDB-v1.4.0, read 2026-10-12), so the player notes say "ItemDB v1.4.1 or newer" and ItemDB should ship first.

Peer Review on the prep's self-audit (thread 2616e94e): the untick bullet first said "works again", which claimed a regression that never happened (the raid switch never honoured the untick), and named only officers, although TurnOnForRaid is gated on Comms:CanPush; reworded. The README and CurseForge opt-out sentence now carry the master-looter exception (Settings.MasterLootRaider). GUI/Intro.lua's CARRY-FORWARD comment is rewritten to say the popup shows only the newest version, which is what Tests/locale_spec.lua enforces.

Fixed -- no second window for a roll already answered, after a loading screen

Peer Review, thread 2616e94e, finding 5. Whether GetActiveLootRollIDs still lists a roll the player answered is undocumented, and Blizzard's own PLAYER_ENTERING_WORLD loop re-opens every id it returns with no answered check (wow-ui-source-classic_era Blizzard_UIParent/Vanilla/UIParent.lua:569). So GUI/LootRoll.lua now remembers each game roll this client answers (rememberAnswered, called beside the one RollOnLoot) in ns.db.answeredRolls for 300 s, and LootRollUI:Resume skips a remembered id. A fresh START_LOOT_ROLL on a reused id forgets it (Show without redraw). Correction to the entry above and to ResetState's comment: the risk was never a second AUTOMATIC answer, because auto-roll lives only in ShowMLRoll (the master-loot prompt), not in Show. It was a window put back up for a roll already answered. Blizzard's own frame for such a roll is left to the client. Specs: Tests/lootrollui_spec.lua (+2), mutation-checked. Player notes: whatsnew_v070_rollhang and the matching CurseForge bullet gain "Dibs does not reopen a roll you had already answered" (Dibs' window only; the game's own frame is the client's).

New -- the Planner's Sort menu has a Resistances group

Asked for 2026-10-09 (resist sets) and recorded unfinished until now. The Build tab's Sort menu gains a Resistances submenu with Fire, Nature, Frost, Shadow and Arcane; picking one orders the gear list by that school, highest first. ns.Items.RESIST_KEYS (Data/ItemSources.lua) names the five keys and StatCategory files them under a new resist bucket instead of Defense. No weight scale prices a magic resist (ItemDB Scoring/Vanilla.lua lists armor only), so GUI/Planner.lua's new sortableStats() appends them to GetWeightStats(), and the menu and the label cache now read that ONE list so a sortable key always has a caption. Captions come from the client's RESISTANCEn_NAME globals with an L["filter_resist_<key>"] fallback. Specs: Tests/source_spec.lua (fire resist now files under resist), Tests/plannerdraw_spec.lua (+1, Sort -> Resistances -> Frost re-orders the list; mutation-checked by removing the group). The sort needs ItemDB MINOR 43 or later, which fills the magic-resist values the Classic clients' GetItemStats never reported (Vanilla 1,371 items, TBC 667; Glacial Cloak 22658 now carries 24 frost). On an older ItemDB the menu still works but finds few items. Contract f93b32b3. No version gate in Dibs: the API did not change, only the data.

Fixed -- unticking "Let Dibs handle loot" did nothing under a raid-wide switch

crimsonmane, #dibs 2026-10-11: "once enabled, disabling it does nothing. still has the dibs windows on grouploot". Settings.LootEnabled ORs in the raid-wide switch (ns.db.raidLootOn), and nothing on the raider's client could end it, so the box wrote the player's setting while every loot window stayed on until the group broke up. Unticking now calls the new Settings.DeclineRaidLootOn (GUI/Settings.lua's General tab callback): the switch is dropped here and, when somebody else pressed it, remembered as ns.db.raidLootDeclined so AdoptRaidLootOn refuses the next re-push. The decline is per group, not per officer (Peer Review, thread fff716e1, finding 3): keyed to one officer, a second officer pressing the button switched the player straight back on. That holds for the officer who pressed the button and then unticked their own box, too. It lapses when the player is no longer grouped (OnRosterSettled), and ticking the box again withdraws it (Settings.WithdrawRaidLootDecline, so the saved field has one owning module). Master-loot raiders are unchanged: under a master looter their loot side stays on regardless, as agreed 2026-10-08. KNOWN COST: the lapse is seen by the 5 s roster debounce, so leaving and joining another group inside that window carries the decline across, the same window the raid switch itself has. Specs: Tests/raidlooton_spec.lua (+7, including a different officer's push, and the presser's own decline), Tests/dkpsettingsui_spec.lua (+1, untick declines, re-tick clears the decline).

Fixed -- a pending roll hung the group after a loading screen

crimsonmane, #dibs 2026-10-11: "caused rolling to hang for the group. after disabling the addon and /reload then the timer showed for the item that was hung up". Blizzard re-opens every pending roll on PLAYER_ENTERING_WORLD (wow-ui-source-classic_era Blizzard_UIParent/Vanilla/UIParent.lua:573-577, GetActiveLootRollIDs -> GroupLootFrame_OpenNewFrame). In replace mode our OnShow hook hid that frame because replaced[rollID] was still set, then LootRollUI:ResetState dropped our own frame: a live roll with no window, so nobody could answer it and the group waited out the server timer. ResetState's comment claimed no roll survives a loading screen; that was wrong and is corrected. The new LootRollUI:Resume runs after it and draws Dibs' window for every roll with time left. When Dibs no longer takes a roll (loot handling off, augment mode, an unreadable item) the game's own frame is shown again. That includes WoW Forever's gamepad roll list, which refills from the same call and could be left hidden if its handler ran first (Peer Review, fff716e1, finding 6). Hiding and re-showing the game's frames is now one helper, setBlizzardRollShown; hideBlizzardRoll wraps it (finding 7). Resume calls Show(id, left, true), and a redraw skips UpgradeAlert:OnDrop, because UpgradeAlert wipes its dedupe memory on the same PLAYER_ENTERING_WORLD and would otherwise re-pop the upgrade alert for every pending roll (Peer Review, thread 90fe4ab6, item 3). KNOWN COST: a drop first seen during the login itself gets no upgrade popup. NOT VERIFIED: whether GetActiveLootRollIDs still lists a roll the player already answered; if it does, auto-roll could answer it twice. That needs an in-game check. Specs: Tests/lootrollui_spec.lua (+5). Mutation checks: the session that started this work reported five production lines broken one at a time, each turning its named test red (not re-run since). Later checks, each run and observed: the gamepad re-show, the redraw's UpgradeAlert skip, and the presser's own decline.

[v0.6.0] -- the Buffs tab rebuilt, live rolling, and raid status

Opened as v0.5.3; renumbered to v0.6.0 at release prep (2026-10-09) because live rolling, the per-item roll tabs and raid status are new player-facing features, which makes this a minor release rather than a patch.

Changed -- release notes lead with the Buffs tab

The operator, 2026-10-10: "i like where we are with the buffs revamp, that should be the highlight for this release." The title names it, and whatsnew_v060_buffs moved from Changed to the top of New in both GUI/Intro.lua's UPDATES and the CurseForge Recent Updates. Its text now also says the list is searchable by stat and that, on Classic Era, ItemDB v1.4.0 adds many more foods (DIBSREQ-IDB-010, MINOR 42: Vanilla foods 11 -> 67; TBC already had its foods, so no TBC claim). Stones stay in their own Fixed bullet only, with its wrong-weapon caveat. Peer Review (thread 7b2948d7) found both of those (a Classic-only claim stated for every flavour, and stones announced twice) and a Planner comment that still gave the buff cap as 16; it is 32 (Buffs:Cap). It also found nothing read the page: Tests/locale_spec.lua now asserts UPDATES and the page's newest Recent Updates block have the same bullet count per group, with the page-only LibDBIcon bullet declared by name (+1 test, mutation-checked: moving the Buffs bullet back to Changed in the popup alone turns it red).

Fixed -- release-prep reviews (Peer Review, d25eeeb0 and 9937c49d)

What's New lists v0.6.0 only (+whatsnew_v060_resize, with its LibAceGUIWidgets caveat, also on the CF page); every retired whatsnew_* key deleted. Live MS under a Main-only gate says roll_no_raid_status_main (+2 specs, mutation-checked). Two strings name the "Loot & Rolls" tab; the CF page names no files. The stone bullet says it needs ItemDB v1.4.0; a stone that does not fit says so in its tooltip (buffs_tip_nofit, +1 spec, mutation-checked). Boards retired (D6): docs/AUDIT.md and Tests/HARNESS_CONTRACT.md imported into the writ inbox and deleted, with Tests/auditdoc_spec.lua, which only guarded the board; CLAUDE.md points at the inbox. Test checklist step 9 made version-neutral.

Fixed -- the whole suite could not finish: nocaller_spec scanned the corpus once per export

Found by running the suite in chunks: Tests/nocaller_spec.lua alone ran past five minutes, and with a filter that matched no test it finished, so the cost was in the sweeps. references and specReferences ran a gmatch over the whole corpus for every export. wordCounts now counts every word in one pass, reproducing the gmatch's skip of an occurrence that shares a separator with the previous one, so the counts are meant to be identical; every count-based assertion and ceiling passes unchanged (old and new were not run side by side). Whole suite at release prep (2026-10-10): 6016 passed, 0 failed. No player-visible change.

Changed -- under a master looter, every raider's loot side is on

crimsonmane, accepted by the operator 2026-10-08: "Let Dibs handle loot" governs group loot only. Settings.LootEnabled ORs in Settings.MasterLootRaider() (master loot, group state ready, not the ML). The reminder popup is dropped. Tests/raidlooton_spec.lua +5, mutation-checked. Peer Review (03a3c612): "not the ML" asked AmMasterLooter (window-only), so between corpses an opted-out ML authored loot-log/SR+ entries; now RaidMasterLooterIsMe (+3).

Changed -- the Buffs tab is laid out like the Build tab

The operator: "i'm not really happy with the layout of this page at all". PlannerUI:DrawBuffs now draws every PLANNED buff as a lit toggle chip at the top (pooled per buff key; a click removes it), a slot column with one row per category (raid, world, totems, then each consumable slot) and its picks, and the library's W:CreateSearchBox over a scrolling list of the selected category's options, best EP first, up to 150. The "+ Add" dropdowns and their plain menu search box are gone. Tests/buffsdraw_spec.lua rewritten (7, mutation-checked). Slow open/close fixed: EP and base stats are cached per repaint, and slot/list rows are pooled -- each repaint used to detach a container of rows that stayed anchored to the scroll content, so closing re-laid out all of them (+3 tests). LibItemDB MINOR 42 adopted: stones score via Items:WeaponDamageEP at the PLANNED weapon's base MELEE_WEAPON_SPEED (live speed only as fallback), and Buffs:EntryFits (GetImbueWeapons) parks a stone that does not fit its weapon, e.g. a weightstone on a sword (DIBSREQ-IDB-011, +7 tests, mutation-checked); the food strip is old-library only (weapondamage_spec).

Changed -- every on/off switch in Dibs' windows is a toggle chip

The operator, 2026-10-08, with Grouper's type chips as the reference: "they added toggle tokens, and they look REALLY nice. we should use them where it makes sense", then "can we also clean up dibs settings to use the toggle tokens? i think it will allow us to make the settings much more clean", then "update the windows to use toggle tokens if it makes sense". New C.Toggle (Constants.lua) builds LibAceGUIWidgets' LAGW-Chip (MINOR 40) on a plain frame, or the old checkbox-with-label on a library without chips, and returns one frame with GetChecked / SetChecked / Click / SetLabel / SetOnChange / SetEnabled either way.

  • View filters: the Planner's Upgrades only and Hide 2H, the Raids tab's Group alts, Show ex-members, Show test runs and the Accounts pane's In raid only, the DKP tab's Show ex-members and Group alts, the Loot Log's Show test runs. The Raids tab's list insets moved from -26 to -28 for the 22-tall chip.
  • Settings: B:Check (GUI/Settings.lua) builds a chip, and CONSECUTIVE ones share a line, wrapping at the tab's width; any other row (a number, a dropdown, a header, a button) ends the run, so the next switch starts a new line. The minimap switch goes through B:Check too, with its own get/set pair. Each chip's _dibsID is the setting key it writes.
  • The other windows: the loot table's "Decide main/alt for me", My Main / Alt's "this is my main" (moved up to -239 to clear the dropdown below; Tests/declbuttonoverlap_spec.lua now counts it as a button and names a chip in its failure message), the master looter's priority picker (a pooled chip per name), and the Soft Reserve settings tab's two switches. Only the Buffs picker stays a checkbox: its row is an item (icon, EP, tooltip, shift-click to link), and a chip would take the row's mouse.
  • Peer Review (thread e384723d): Settings lines are packed for the window's minimum width (MainWindow.SETTINGS_MIN_W), since a tab is drawn once and narrowing it used to push chips past the edge; a spec pins that a chip has a width when made and SetLabel resizes it; and one pins that the enchanters' disposal roll never lands on the item's roll tab. All three mutation-checked.
  • Specs find toggles by the _dibsToggle marker or their _dibsID, and set one by clicking only when it is not already in the wanted state (a chip's click flips it, so "SetChecked then Click" flipped it back). Tests/constants_spec.lua drives both paths of C.Toggle; Tests/settingsui_spec.lua pins the line sharing, mutation-checked (one toggle per line turns it red). Not seen in a game client.

New -- live rolling: the Gargul-style loot window, as an officer option

crimsonmane (#dibs 2026-10-01); the operator: "i'll leave that as is as an option, then build this new". Officer setting lootTableStyle ("Loot window", Loot & Rolls): table (default, unchanged) or live. The master looter stamps it into the table (t.live, TablePayload.live); both faces open it locked and executing (openLive), so there is no picking phase. The rows lose Main / Off / Pass; a bar above the roll tabs reads Rolling on [item], then Winner! name, with MS / OS / Pass that throw a real /roll (Pass is /roll 1) once per row (UI:LiveRoll), greyed with the reason when nothing is called, already rolled, not listed, or barred by raid status. OnRollNow rolls nothing for anyone on a live table. The roll tab is sorted MS, OS, Pass, highest first, with everyone eligible who has not rolled as Yet (UI.LiveList). The master looter counts a 1-1 roll as a pass (ML.LivePass), never as a roll. A row's item tooltip names its competitors (UI.RowTooltipText, both styles; BindFrame's extra now follows the BiS block). Late rows are closed at once. EPGP and auction tables keep their own flow. The master looter awards (crimsonmane, 2026-10-08: "masterlooter should pick the name and press Award, because someone might roll on something they shouldnt"): a live row is called with manualAward, so LootSession.Resolve turns the rolls into a ranked award list (tier, then roll; no tie-break) and the phase holds at "award". The list lands on the live bar (state.liveAward, top roll picked); a click on a roll-tab line picks someone else (UI:PickLiveAward); Award goes through ML:AwardTo -> ResolveRoll, as EPGP's does. The two-part table still awards on the die. Raiders learn the rolls closed from loot_table_award (ML-gated, also in the reload catch-up): UI:OnAward greys the bar and UI:LiveRoll refuses until the result (+4 tests). /dibs abandon then a re-call of the same row left raiders latched (myRolled), the old rolls on the tab, and the award open: RollPayload now carries a call token (sender:time:counter) and a new one resets all three, both table styles (+3 tests). A handover clears the old call and award (AdoptMasterLooter). loottablegate_spec sweeps every groupOnly type. Raid assist is not built; Take all is the Distribute Later setting (docs/20). Eight tests in loottableui_spec and four in livepass_spec (live and two-part), all mutation-checked. Peer Review (thread ea5329b9): MS / OS / Pass only asked the call's only list, so on a soft-reserved or Competition row under Standard a non-competitor could throw a /roll the master looter then refused. Both the painter and UI:LiveRoll now ask liveListed, which adds LootTable.Eligible. Also pinned: a setting change mid-table leaves the open table's style alone. loottableui_spec now loads ItemInteract, as the TOC does, so the row hover is driven for real. All mutation-checked. Not seen in a client.

Changed -- LibAceGUIWidgets MINOR 41 adopted: windows re-lay-out during a drag on their own

ClearFrame now calls DoLayout from its own onResize (DIBSREQ-LAGW-003, which Dibs raised), skipping a second layout when AceGUI's content hook already ran for that size. Dibs' staged copy, C.LiveRelayout, is deleted with its two calls in GUI/MainWindow.lua and its five tests: on MINOR 41 it would have replaced the library's onResize and lost that guard. A player still on MINOR 40 gets contents that settle at drag-end, as before the helper existed. Not dragged in a client.

New -- the loot table shows every roll, one tab per item

The operator, 2026-09-30, answering Crimsonmane's "people did not like not knowing (not seeing) what everyone else rolled": "add a tab to the current rolls window that shows rolls per item ... it would auto generate 3 tabs, 1 for each piece of gear. and as the rolls come in, populate them into the correct tab", with "< and > arrows at either side of the tabs", and "this should be in the widgets library".

  • The strip is LibAceGUIWidgets' LAGW-TabStrip (MINOR 40, delivered on contracts thread cb965300), detected by AceGUI:GetWidgetVersion("LAGW-TabStrip"). With an older library the window is unchanged.
  • The panel (GUI/LootTable.lua, paintRolls) takes the EPGP board's slot under the row list -- the two never meet, since an EPGP table throws no dice -- and appears once the master looter calls a row. One tab per row in table order; the shown tab follows the row being rolled until the player clicks another, and the next row the master looter calls takes it back. Each line is the roller, the number, and Main / Off spec / Tie-break or the range thrown (UI.RollLine); the winner's line turns green once the row ends. Not on auction or EPGP tables.
  • The wire: a new loot_table_rolls broadcast (LootTableSync.RollsPayload, master-looter gated like the other table broadcasts). The chat handler in GUI/Masterlooter.lua now hands onRollSeen the roll the tally COUNTED (nil on a refusal), so only counted rolls appear -- a refused roll is already reported to the master looter and did not count. Live rolls go one per message; a reloading raider's catch-up (UI:OnTableAsk) sends each rolled row's whole list with full, which replaces rather than appends. The master looter ignores its own echo. All of it is cleared with the table (OnTable, StartTable, ResetState), so a new corpse starts with no old tabs.
  • Fixture defect found on the way, fixed: Tests/loottableui_spec.lua's "the window never appears on its own" block gave its second namespace this file's own ns.Comms, so its copy of LootTableSync re-registered every message type over the file's real ones; any broadcast asserted in a later block was provided from a window with no table and pushed nothing. Its RegisterSyncType is now a no-op.
  • Eleven tests in Tests/loottableui_spec.lua ("the rolls per item"), mutation-checked: removing the broadcast, the full replace or the master looter's echo guard each turns one red. Not seen in a game client.

Fixed -- the Planner's Reset button left every gear-list filter in place

The operator, 2026-10-05, from WoW Forever: "bug: the reset button on forever isn't clearing out the filters". The loadout bar's Reset (GUI/Planner.lua, resetBtn) only called Loadout:ResetToEquipped, so Upgrades only, the search box, the source dropdown, the type dropdown and the sort stayed as they were. The filters live in locals of the Build-tab draw and Reset never touched them. Nothing here is flavour-gated, so Classic Era and TBC had the same bug. New PlannerUI.ResetFilters clears all six, Hide 2H included, through the same setters the dropdowns and ApplyView use, so the saved copy in DibsDB.filters.planner is cleared too and a /reload does not bring them back. Reset now calls it after the loadout reset; loadout_reset_tip says so. Pinned in Tests/plannerdraw_spec.lua.

Fixed -- CurseForge was told LibDBIcon is embedded, so the app never installed it for Era or TBC

The vendored Libs/LibDBIcon-1.0/LibDBIcon-1.0.lua arrived with --@curseforge-project-slug: libdbicon-1-0@ on line 1. The BigWigs packager reads that marker from every packaged .lua and records the slug as an embedded library, overwriting the required-dependencies entry .pkgmeta set (release.sh:1078, then :1880-1890). Dibs.toc and Dibs_TBC.toc hard-depend on LibDBIcon and do not load the copy (only Dibs_Camelot.toc does), so a CurseForge-app install on Era or TBC got no LibDBIcon. Silent: the packager exits 0. Marker deleted; Tests/pkgmetaclaims_spec.lua now sweeps every vendored file the Forever TOC loads for it, with a floor, mutation-checked (red with the marker restored). Reported on inbox threads ec639087 (TOGBankClassic) and c7938f3e (Peer Review).

New -- raid status: which guild ranks may roll in a raid

The operator, 2026-10-05: "it should decide whethere they can roll in a specific raid. like if you set you 'progression' raid as AQ40 for example, you have to have AQ40 status to roll on an item, or to roll main on an item. so we need to be able to set it and have it enforced", and earlier the same day "this needs to be an officer setting and synced, so the auto roller accounts for it."

  • Model: new Modules/RaidStatus.lua. A raid may carry one rule: the guild rank indices (0 = GM, stored by index so a renamed rank cannot split clients) that hold its status, and a mode -- "roll" (no status, no roll at all) or "main" (no status, no Main Spec roll; "ms" and soft reserve's "main" are both the top tier, a tie-break "tb" is not). A rule with no ranks ticked is not enforced, the same call LootSession.IsEligible makes for Manual with nobody flagged. A player the roster does not know (a pug, or the roster not loaded yet) holds no status. Loaded after Modules/RaidSystems.lua in all three TOCs.
  • Sync: a raidStatus field in Settings.SerializePush / ApplyPush, under the same three rules as the other per-raid officer maps (keyed by raid, absent-means-silent, replace-not-merge); every edit calls Settings.Touch, so the newest stamp wins.
  • Enforced by the master looter: LootSession.New takes an injected statusGate; AddRoll and AddChoice refuse with the new reason no_raid_status (refused, never quietly re-filed as off-spec). LootTable.New takes the same gate and LootTable.Select refuses a barred pick; a pass is never barred. GUI/Masterlooter.lua builds the gate from RaidStatus.Gate(raidKey) in both startRoll and RunTableRow, and the CHAT_MSG_SYSTEM handler always prints ml_roll_no_status (debug on or off), because the likeliest roller is a raider without Dibs. A refused choice reaches the raider as lootack_reason_no_raid_status.
  • Enforced on the raider's own windows: GUI/LootRoll.lua's ML prompt and GUI/LootTable.lua's rows grey the barred buttons with the reason (greyed, never hidden; Pass untouched). The auto-roller moves a decided Main to Off-spec when only Main is barred, rolls nothing when rolling is barred, and UI:OnRollNow will not throw a pick made before a rule arrived. Both table builders (the master looter's and the raider's) carry the gate, so a raider who takes over as master looter keeps enforcing it.
  • Officer UI: a Raid status block on the Loot settings tab, one dropdown per raid: the three modes, then a tick per guild rank (GuildControlGetRankName, which is 1-based and which the Classic Era calendar's mass-invite menu reads for any member; LibGuildRoster's rank names are the fallback). Greyed for anyone who is not an officer, and a click from a non-officer is ignored. New general row builder B:Menu for a dropdown whose menu the caller builds. New Comms:GuildRankOf(name).
  • Peer Review (thread 331a2c79), all three taken: (R1) a rank index is a position, so a rank the GM inserts above a ticked one silently moves the status; each ticked rank's name is now recorded beside it, RaidStatus.MovedRanks reports any that no longer match, the settings row says "(a rank changed -- check)" and the master looter gets one chat line per raid per session (ml_raidstatus_moved). It is a warning only: enforcement stays on the index so two clients still agree. (R2) the single-item Roll's gate is now pinned through ML.RollSlot in lootmethods_spec. (R3) the master looter's raid key now rides the roll (roll.raid) and the table (TablePayload.raid, t.raid), and every raider window gates on that key, falling back to its own CurrentRaid only for a master looter on an older build -- so the two cannot disagree about which raid it is.
  • Tests: new Tests/raidstatus_spec.lua (27), plus raid-status blocks in lootrollui_spec (6), loottableui_spec (6), settingsui_spec (6), strayroll_spec (2) and lootmethods_spec (2). Every production line they name was mutation-checked: broken, seen red, restored. Nothing here has run in a client.
  • constants_spec's C.HoverWhileDisabled site count for GUI/Settings.lua goes 5 -> 6 for B:Menu.
  • DKP / GDKP bids, when the officers turn it on (2026-10-08). The operator: "only if the officers turn it on. these need to be settings so each guild can chose". New officer setting raidStatusBids ("Status also needed to bid", Raid status block), off by default and in the push. On, DKPAuction.Bid refuses a bidder without status with no_raid_status (checked after pug eligibility, before the bid's own rules), and the bid popup greys Bid and says why (DKPBidUI.PaintStatusGate); Pass stays live. A bid is judged as a Main Spec claim, so mode "main" bars it as "roll" does. EPGP needed nothing: its award list is the table's picks, which the gate already checks. Five tests in raidstatus_spec, three mutations each seen red.

Changed -- .pkgmeta carries no comments

The operator ruled out comments in .pkgmeta entirely on 2026-10-03, and ClassicThreatLogger's review found Dibs' still had two comment blocks. Both are removed. What they explained (no trailing # on a list item, bare folder names, single-star quoted globs, every hard dependency listed explicitly) now lives in CLAUDE.md's Testing section. Tests/pkgmetaclaims_spec.lua gains "carries no comment line at all" so a comment cannot come back unnoticed. No player-visible change.

Changed -- the library-contracts board is retired

docs/LIBRARY_CONTRACTS.md was imported into writ's inbox and deleted, and docs/README.md and the C.LiveRelayout comment in Constants.lua no longer point at it. The one request still open, ClearFrame re-laying out its contents during a resize drag, went to LibAceGUIWidgets as a contract; MINOR 41 shipped it (above).

All of v0.5.2 (the first half on 2026-10-16, the rest on 2026-10-15), v0.5.1, v0.5.0, v0.4.7, v0.4.6, v0.4.5, v0.4.4, v0.4.3, v0.4.2 and v0.4.1, and every version from v0.4.0 back, have moved to CHANGELOG_ARCHIVE.md -- this file has to stay under GitHub's 125,000-character release-body limit, which Tests/pkgmetaclaims_spec.lua asserts at 120,000 rather than leaving to whoever happens to remember it. Released versions go across a few sub-sections at a time, oldest first; the archive's own header records each move.