Dibs-v0.8.0
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.luaSPEC_MAP[7]gainstank, so it reachesItems.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].tankon 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, andItems:ScoreForClassSpecscores it like any other stock spec. - Dibs' interim stand-in is gone: the block at the top of
Modules/Weights.luathat registered a private "tank" scale,DefaultTemplates'add("tank"),ScoreForClassSpec's fallback to that source, thetemplateLabel"(tank)" branch inGUI/Weights.lua, and theweights_tank_templatelocale key. A savedsource = "tank"(written by a dev build of this version) is folded to the stock scale by one local,live, on both branches ofWeights:Activeand inSaveCustom, 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
GetBiSWeightsanswers nil for a spec the scale lacks, so the data is the detect. NewWeights:MissingScaleSpecnames 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: shamantankis 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.mdno longer lists awardenshaman token that is not in the data. Modules/SoftRes.luaCanReserve:tierannotatedstring?, 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 matchesns.GetLocale()["key"]andC.GetLocale()['key'], not onlyL["key"], so a typo in a direct lookup (the/dibschat 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
/dibsrouting stubs may fake (ROUTEDinTests/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.luaload in the no-C_AddOnstest unregisters theADDON_LOADEDframe 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.CLMglobal 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 fromminGP, each EP award (0 when CLM has it switched off), the interval in minutes, standby EP asfloor(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.SolveItemLevelsolves atgp + 0.5and 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.ApplySettingsprices every written item again afterwards and reports any that no longer match. - Pasted JSON export (
Import.CLMSettingsFromExport): CLM's export carries only the flatconfig, 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 keysimport_settings_*,import_set_*,import_why_*andimport_usage_settings. The roster pickerchooseRosteris 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'stypefield is compared with them. A roster whose type cannot be read still previews, with a note asking the officer to check. minGP = 0became a base GP of 0, which leaves every raider with no GP without a priority (EPGP.PRanswers nil). AminGPbelow 1 is now a note, not a write.Settings.MIN.epgpBaseGP = 1refuses it from a hand edit or a push too. NewSettings.MigrateBelowFloor, run fromDibs.luaat ADDON_LOADED besideMigrateEarnMode, 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 (ApplySystemwrites throughSettings.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 inTests/bootstrap_spec.lua(one holds the chat line's labels toSettings.MIN) and one inTests/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_failedwith 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 ""fromsettingsSubjectinGUI/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 whilelootCouncilOfficersis on; the rest come from the typedlootCouncillist. - Session (
Modules/LootSession.lua): acouncilmode 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 bothAddChoiceandResolve. - Loot table and master looter (
GUI/LootTable.lua,GUI/Masterlooter.lua): newML:CouncilTableRow, cloned fromAwardTableRow;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 carriescouncilandvotes; a newloot_council_votemessage that only the raid's master looter acts on. - Settings:
lootCouncil(default empty) andlootCouncilOfficers(default on), officer-owned and synced, on the Rolls tab beneath Distribute Later.B:Texttakes an optional normalizer so the list saves throughQualifyList. 24 new locale strings. - Peer Review fixes (threads 565dc454 and 28ba50a0):
VotersFormatched 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 throughNames.Fullbefore the membership check, so "Ana" from another realm is not counted as the council's "Ana-RealmB". - Removed:
councilas a loot-method input mode inModules/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 isepgp_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 asepgpRosters; removing a roster deletes no points. - A raid's roster is
pointsRosteron 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 thepointsRosterAtfix 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, pooledEPGP.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.mdwith 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.LootSessionAddChoice's council branch takes Main / Off spec / Bid plusr1..rNfor the N answers that row offered, throughLootCouncil.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;Candidatesranks configured answers first and carries the note.Modules/LootTable.luarows carryresponsesandSelect/Adopttake a note;Modules/LootTableSync.luasends the note sanitised and the board carries it on council boards only.GUI/LootTable.luadraws the buttons, a Note button and its popup (UI:SetNote,UI:PromptNote). - Setting
lootCouncilResponses, officer-owned and pushed withlootCouncil, on the Rolls tab. - The council vote now fails closed (Peer Review thread 28fba7ce):
UI:OnCouncilVoterefuses a vote whenNames.Fullis 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.luarecordDisposal), 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 withMainOf),ConfirmedMainandPoolMainOf. 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'sbyis 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 anddkpAltSpend(SpendableOf/ChargeFor, viaDKP.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) andDKP.LiveStandbyresolved accounts through the bareDeclaration.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 takePoolMainOf. Cost: an unlinked alt raiding while its main is benched earns both, as two unrelated characters would, until an officer links them. Two cases inTests/poolwriters_spec.lua, red against the old lines (run 20261014-092410.665-1048316769). TheDKP.SpendPoolcomment now namesPoolMainOf; 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.AccountKeyOfandAccountCharsresolved the lock's account through the bareMainOf, 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 takePoolMainOf. 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 savedlockedflag and stays locked. Three cases inTests/officerlink_spec.luared against the old lines (run 20261014-093840.206-3822737171) plus one pinning that an officer-linked alt still shares the lock; threeTests/declunlock_spec.luacases 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 deterministicDKP.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 exactlyfloor(|total| * keep)per account (EPGP.PooledDecayDeltas) and now writes in sorted order. The starting balance is paid once per account (NewcomerstakesmainOf); 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/ResetPlanfeed 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.KnownRosterscounts 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
itemcharge 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), throughDKP.MayAuthor, the gateDKP.UndoAwarduses. 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 forCanManageLoot, which admits a loot leader with no guild rank, and every other client'sLedgerSync.Authorizerrefuses 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
CanManageLoottab, so a loot leader without guild rank wrote corrections only their own client kept. Both buttons now refuse withoutComms:IsOfficer, saying why (epgp_adjust_err_not_officer).Tests/epgpsettingsui_spec.lua's officer-action block now states its officer stub instead of inheriting aCanManageLoot-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.Heightcounts 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:
epgpTokenPricesridesSettings.SerializePushas its own field, absent-means-silent and replace-on-adopt, likeepgpSlotModifiers. - 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.AcceptLinksso 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). NewEPGP.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);PriceItemtries the officer's price, then this default, then the item's own. It feature-detectsGetTokenRewards, so an older ItemDB leaves the token unpriced as before. ItemDB's comment onGetTokenRewardsnames aGetSlotit does not have; Dibs uses its ownItems:GetSlotandItems: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)paysEPGP.BossEPFor(encounterID); DKP'sENCOUNTER_ENDframe now passes the id through. - Push:
epgpBossValuesridesSettings.SerializePushas its own field, absent-means-silent and replace-on-adopt, likedkpBossValues. - 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.CeilingFortakesbossEP, andTightestCeilingabossEPOf(profile), so the warning sums the officer's per-boss values (EPGP.RaidBossEP) instead ofbosses 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;AwardBossnow calls it.EPGP.OnRaidStart/EPGP.OnRaidEndare called fromRaidController.Opened/RaidController.Settlebeside DKP's, with the record's last-seen stamps.EPGP.IntervalTickandEPGP.StartIntervalTicker/StopIntervalTickermirror DKP's ticker, armed and stopped from DKP's group-event frame.- Every route goes through one local
earnersForinModules/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, forDKP.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.OnCommittedre-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 bonusEPGP.CeilingForalready 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/Settleturned?into a nil key meaning "guild default", butDKP.OnRaidStart,DKP.OnRaidEnd,DKP.ApplyTimeFloorandEPGP'searnersForall resolved a nil key throughCurrentRaid(). 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 passunkeyed = true, and both systems ask the newRaidSystems.ActiveFor(system, raidKey, unkeyed), which answersGuildActivewhen unkeyed andActiveotherwise. A direct call with no key still means "the raid I am in", whichTests/concurrentraids_spec.luapins. - DKP's on-time bonus folded alts by the wrong record's stamps.
DKP.AwardOnTimecalledearningwith no stamps, so the once-per-account fold readRaidController.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.Openednow builds one options table, stamps included, for both, andDKP.OnRaidStartforwardspresent.
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/Settleonly treatedRaid.UNKNOWNas unkeyed, so a record with a nil key passed nil with nounkeyed, andDKP.OnRaidStartfell straight back toCurrentRaid()-- the bug above, by another door. NewRaid.AwardKey(r)is the one spelling: nil,""andUNKNOWNall answer(nil, true). Both hooks use it.StartandAcceptnever write such a record, so this needs a corrupt or foreign copy; specs driveOpenedandSettledirectly with both shapes. - EPGP's
earnersForguardedActiveForand 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-fielddisables inTests/attendancecommit_spec.luaandTests/intervalrearm_spec.luaare narrowed: the first routes its thirteen re-points through onestandInhelper carrying a singledisable-next-line, the second marks each of its four lines. DKP.Enablednow says in its docstring that a record-keyed route asksActiveForwithRaid.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)writesr.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 ofRaid.Accept(the same-envelope branch reports"merged"), inRaid.SyncStampand inRaid.EditDigest(ab: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.FilterEditskeeps a bench entry only when its author is the player (Raid.SamePlayer, the one spelling the sender'sRaidController:SetBench/AmBencheduse too) or passes the officer-edit test.RaidController:SetBench(player, on)applies the same rule before writing;playernil 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'sbyis 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 theamendcaveat carries. Both are written atRaidSync.FilterEdits. - Standby points: new officer settings
epgpStandbyEPanddkpStandbyValue, 0 by default, both inSettings.SYSTEM_KEYSand on their tabs. At a kill, after the boss award and behind its gates,EPGP.OnEncounterEndandDKP.OnEncounterEndpay the live bench throughDKP.LiveStandby/DKP.StandbyOnly, which keeps only benched players the guild roster shows online at the kill (LibGuildRosterIsOnline, 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 newEPGP.REASON_STANDBY("standby", DKP's spelling via the newDKP.AwardStandby), and the DKP history labels it "Standby" (dkp_reason_standby).DKP.AwardStandbyapplies only the pug rule (EarningRaiders):StandbyOnlyalready folded once per account, and the second foldearningran 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:
IsOnlinereads LibGuildRoster's roster table, which is not complete right after the payer's login or reload (IsRosterCompletefalse), so every benched player read offline and the kill paid the bench nothing, silently.DKP.LiveStandbynow checksIsRosterCompletefirst: 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 withoutIsRosterCompleteis trusted as complete (thread a6d3d0a6 item 1). The line printed on every client at first, becauseDKP.OnEncounterEndruns everywhere and the payer check sat insideAwardStandby, 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 onmayAuthor(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. NowRaidController.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 pureRaid.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 newRaidController.Mine()is the pick only when it ranks above 0. Everything that pays, stamps or holds readsMine: the standby payout (RaidController.MyBench, read by bothOnEncounterEnds), 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.Startrefuses only on an open record that is mine or my group's (RaidController.Blocks), stale or not.RaidController:SetBench(player, on, canon)andBench/AmBenchedtake an optional canon;BenchedOn()finds the raid I am benched on, andBenchNeedsPick()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 keyraid_bench_pick), and the strip shows and leaves whichever raid I am benched on.Raid.Liveis removed; its spec callers useLiveFor. Callers stubbed with onlyLive/Benchstill work, because each payer readsMine or LiveandMyBench 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 andstartedAtbreaks 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 toBlocks, 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 onstartedAt. 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 onstartedAt, and oneSnapshotwrote the leader into it, after which it kept the night. NewRaidController.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 inBelongs, soLive()and theSnapshotthat 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
StandbyOnlyonly knows the paying raid's kill roster.RaidController.MyBenchnow 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. AndSetBenchrefuses a self sign-up (in_other_raid, said in chat asraid_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 withneeds_pick, and the End button opens a menu of the open raids under the headingraid_end_pick, then confirms the one picked. The left-the-group prompt ends the raid it named, by canon. OpenRaidssorted the whole archive on every repaint (item 1). It walkedAll(), 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.inMyGroupandSnapshotalso share one group walk (groupMembers) instead of two copies (item 6).
- 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
- 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.BenchStripandTrackerUI.BenchMenuItemsare 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 ownDecodeNotepattern. 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 ispoints, GP isspentminus the base; for a DKP rosterpointsis 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) andDKP.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.StandingOfleftTests/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.FoldSchoolStatsnow 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 readsGetItemStatson a suffixed link in a Classic Era client. - The three field-7 readers are tied together.
ML.ItemKey,Declaration.SuffixOfand the zeroing rewrite inItems:SuffixStatseach read the suffix field; newTests/fieldseven_spec.luaruns 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 ofItemKeyinto 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 throughB:Checktoo, with its own get/set pair. Each chip's_dibsIDis 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.luanow 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 andSetLabelresizes 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
_dibsTogglemarker 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.luadrives both paths ofC.Toggle;Tests/settingsui_spec.luapins 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 byAceGUI: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_rollsbroadcast (LootTableSync.RollsPayload, master-looter gated like the other table broadcasts). The chat handler inGUI/Masterlooter.luanow handsonRollSeenthe 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 withfull, 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 ownns.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. ItsRegisterSyncTypeis now a no-op. - Eleven tests in
Tests/loottableui_spec.lua("the rolls per item"), mutation-checked: removing the broadcast, thefullreplace 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 callLootSession.IsEligiblemakes for Manual with nobody flagged. A player the roster does not know (a pug, or the roster not loaded yet) holds no status. Loaded afterModules/RaidSystems.luain all three TOCs. - Sync: a
raidStatusfield inSettings.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 callsSettings.Touch, so the newest stamp wins. - Enforced by the master looter:
LootSession.Newtakes an injectedstatusGate;AddRollandAddChoicerefuse with the new reasonno_raid_status(refused, never quietly re-filed as off-spec).LootTable.Newtakes the same gate andLootTable.Selectrefuses a barred pick; a pass is never barred.GUI/Masterlooter.luabuilds the gate fromRaidStatus.Gate(raidKey)in bothstartRollandRunTableRow, and theCHAT_MSG_SYSTEMhandler always printsml_roll_no_status(debug on or off), because the likeliest roller is a raider without Dibs. A refused choice reaches the raider aslootack_reason_no_raid_status. - Enforced on the raider's own windows:
GUI/LootRoll.lua's ML prompt andGUI/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, andUI:OnRollNowwill 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 builderB:Menufor a dropdown whose menu the caller builds. NewComms: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.MovedRanksreports 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 throughML.RollSlotinlootmethods_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 ownCurrentRaidonly 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 inlootrollui_spec(6),loottableui_spec(6),settingsui_spec(6),strayroll_spec(2) andlootmethods_spec(2). Every production line they name was mutation-checked: broken, seen red, restored. Nothing here has run in a client. constants_spec'sC.HoverWhileDisabledsite count forGUI/Settings.luagoes 5 -> 6 forB: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.Bidrefuses a bidder without status withno_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 inraidstatus_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.
All Relations
- All Relations
- Embedded Library
- Optional Dependency
- Required Dependency
- Tool
- Incompatible
- Include