promotional bannermobile promotional banner

Ultimate Dibs

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

UltimateDibs-v0.8.2

File nameDibs-UltimateDibs-v0.8.2.zip
Uploader
PmptastyPmptasty
Uploaded
Oct 28, 2026
Downloads
98
Size
2.0 MB
Flavors
Classic TBCClassicForever
File ID
9353961
Type
R
Release
Supported game versions
  • 2.5.6
  • 1.60.1
  • 1.15.9

What's new

Changelog

[v0.8.2] -- the enchanter search finishes, and switching Dibs off reaches the raid

Changed -- release prep for v0.8.2

A patch: both entries below are fixes. The What's New popup (GUI/Intro.lua UPDATES) now lists v0.8.2 only, under one Fixed heading (whatsnew_v082_deask, whatsnew_v082_raidoff), with the known-issues bullet rewritten as whatsnew_v082_known. Every whatsnew_v081_* key left Locale/enUS.lua, per the rule that a retired bullet is a string nobody sees. The CurseForge page gains a v0.8.2 Recent Updates block and drops v0.6.0's to keep five versions. The two "ClassicCalendar" mentions on that page and the two in README.md now say Ultimate Calendar (Peer Review, thread 19546a89).

Fixed -- the enchanter search could hang at "2 of 3" for good

crimsonmane, #dibs 2026-10-22: a three-player raid, all on Dibs, the search for an enchanter showed "2 of 3 answered" and never closed. Two defects in GUI/LootTable.lua OnDeAsk, both mine. The raider's "already answered" latch (state.deAsked[key]) was per ROW for the whole table, so a second ask on a row (after one closed with nobody, or on a row reopened by the v0.8.1 no-winner fix) was read as a re-send of the first and never answered by anyone who had answered before. And the latch was set BEFORE LootTableSync:SendDeOffer, so a whisper the transport refused (Comms:SendTo returns false, the roster-guard case documented above it) was never retried by the master looter's later re-sends. Now AskEnchanters stamps each ask with a token (name, client clock and a counter, so a handover or a reload cannot reuse one) carried on the ask and every re-send (Disenchant.AskPayload gains an optional ask; BroadcastDeAsk passes it through), a raider answers each new token once, and the latch is set only when the send went out. An older master looter sends no token and the raider falls back to the old per-row latch. Specs: loottableui_spec +3, disenchantdiscovery_spec +1, mutation-checked (a per-row latch, an unconditional latch and a constant token each turn a named test red).

Fixed -- switching Dibs off left it on for the raid

crimsonmane, #dibs 2026-10-22: "Entered ZG with Dibs enabled, switched it off in the settings ... other players ... keep getting popups." "Turn Dibs on for the raid" only ever turned raiders ON; unticking the box on the officer who pressed it ended it on that client alone (Settings.DeclineRaidLootOn), and every raider they had switched on kept Dibs' loot windows until the group broke up. Now that untick pushes the withdrawal, group-only, as its own field raidLootOff (not raidLootOn = false, so an older client, which tests == true, reads silence), and Settings.RevokeRaidLootOn drops the switch on a raider whose switch came from that same sender, with a chat line (raidloot_off_chat). It never ends another officer's switch, never one this client pressed itself, and never touches anybody's own "Let Dibs handle loot". The withdrawal lapses with the group and is cleared when the officer presses the button again. The button's tooltip says so. Specs: raidlooton_spec +7, mutation-checked one at a time (the push, the field, the receive line and the same-sender check).

Changed -- two more v0.8.0 entries moved to the archive

The two entries above took the live file to 122,346 characters against the 120,000 ceiling, so v0.8.0's EPGP per-boss EP and EPGP token prices entries moved to CHANGELOG_ARCHIVE.md, oldest first as before.

[v0.8.1] -- other guilds stay out of your raids and points

Changed -- the addon is rebranded "Ultimate Dibs"

The operator, 2026-10-21: "we're rebranding, lets update the addon to Ultimate Dibs", matching the CurseForge project name (slug unchanged, dibs). Display only. C.DISPLAY_NAME in Constants.lua is now "Ultimate Dibs", which renames every window title, the status bar, the minimap button and its tooltip, the tooltip headers and the chat prefix C.CHAT_PREFIX in one place. All three TOCs' ## Title follow. Eighteen chat lines in GUI/SoftReserve.lua hardcoded their own |cffff8800Dibs|r: prefix (a second spelling of the brand, in a slightly different orange, that C.CHAT_PREFIX existed to replace); they now use the constant. The "Dibs: " fallbacks in Dibs.lua and GUI/Import.lua match. Three locale strings that introduce the addon by name say Ultimate Dibs: the minimap click hint, the raid-chat void line, and the manual's opening sentence. The player docs say Ultimate Dibs throughout (operator: "update the docs for UD instead of just Dibs"): README, the CurseForge page, the in-game manual and the v0.8.1 popup notes. Kept: quoted UI labels ("Let Dibs handle loot", "Turn Dibs on for the raid"), /dibs, the Dibs folder, and released versions' notes.

Deliberately unchanged, because renaming any of them would break players: the folder and TOC file names (the client identifies the addon by folder, and Dibs.toc / Dibs_TBC.toc / Dibs_Camelot.toc are its per-flavour loaders), DibsDB (renaming it orphans every saved variable), C.ACE_ADDON_NAME (the minimap icon's saved position is keyed by it), the comms prefix (it would split sync with older clients), the LibLocaleOverride registry key, and the /dibs command. Tags move to UltimateDibs-v from this one. A rename bullet leads the v0.8.1 Changed group on the CurseForge page and the What's New popup (whatsnew_v081_rename).

Changed -- release notes for v0.8.1

The CurseForge page's Recent Updates and the What's New popup (GUI/Intro.lua UPDATES, whatsnew_v081_* in Locale/enUS.lua) carry v0.8.1, and v0.5.2 left the page so it keeps five versions. Peer Review (thread 96f3c91c) found four notes that promised more than the code does, each checked against the known costs above: the points bullet now says it takes effect once the other guild is on v0.8.1 and that awards already delivered stay (the winner and subject rules let unstamped entries in); the settings bullet says players already hidden stay hidden (an archived name returns only when it is back in our roster); the known-issues note says a raid you were in, one at least half made up of your guildmates, or one a guildmate led in their old guild is kept on purpose, and that a character in no guild keeps what it had; and the pug-standings cost is now a Changed bullet on both ("joins your group", since the ledgers ride the group leg), so the popup gains a Changed group. README's pug section names the master looter or officer and unlocks, matching the page, and docs/24-test-run-checklist.md uses a current key as its example. To make room, v0.8.0's boss-kill EP fix moved to CHANGELOG_ARCHIVE.md. The no-rolls fix below (2026-10-20) adds one Fixed bullet to all three surfaces (whatsnew_v081_norolls), and the rebrand above (2026-10-21) leads the Changed group (whatsnew_v081_rename).

Fixed -- a loot-table row nobody rolled on could not be rolled again

Discord (crimsonmane), 2026-10-20: "if no winner, unable to roll an item again." Confirmed by reading: when a row's roll ended with nobody rolling, or with every tied player passing, ML:ResolveRoll handed the table kind = "empty" and LootTableUI:OnRowResolved resolved the row through LootTable.MarkRolled with a nil winner. That part is right (the sequencer must not hand the row back for ever, and Tests/loottablebid_spec.lua pins it for the no-bid roll fallback), but it was the end of the road: a resolved row has no Roll or DE button (canRoll asked for pending), LootTable.GoTo refuses it, and LootTable.Void reopens only a row whose winner is voided, so an item still on the corpse was loot nothing would offer again. docs/04 "Tie-breakers" always said the item "stays with the master looter to re-run, hand out by council or shard". I built the resolution and never built the way back.

  • The row is recallable. New LootTable.Reopen(t, key) puts a resolved row with NO winner back to pending, reopening execution when the table had finished on it (the trap door Void already opens); a won row is refused, since undoing a win is Void's job and Void announces. The window decides which winnerless rows qualify (recallable: not disenchanted, not banked -- the two endings where the item did go somewhere) and reopenIfRecallable also clears the stale "No winner" from the cell.
  • Roll and DE stay on such a row, on a finished table too (canRoll now admits done and a recallable row), and RollRow and DisenchantRow reopen the row before GoTo / the pending check. The master looter's status row says nobody rolled and names the two ways on (loottable_status_norolls), after the refresh so a last row ending this way does not read "Every item has been rolled"; the missing-roller list rides along.
  • Every raider's copy reopens on the row's call. OnRollNow runs reopenIfRecallable first, so a picked raider's /roll fires and a live-rolling raider's MS / OS / Pass light again for the row (LiveRoll refuses a resolved row). The raider's status row says the master looter can call it again (loottable_status_norolls_raider) when the loot_table_result arrives with kind = "empty", which it already carried. Nothing on the wire changed.
  • Known cost: a raider still on v0.8.0 whose row reads "No winner" does not reopen on the re-call, so on a live-rolling table their buttons stay grey for it; their window's cell is cosmetic on a picked table, where the re-call still fires their /roll. The master looter's tally counts every /roll either way.
  • loottable_spec +4 (Reopen: a no-winner row comes back, a finished table reopens, a won row is refused, a pending row, an unknown row and a selecting table are refused) and loottableui_spec +3: the master looter's face keeps Roll and DE on the row, tells the raid, names the item on the status row and a click on Roll reopens the row and reaches the master looter's roll; the raider's row reopens on the call; a live-rolling raider's bar closes on the ending and lights again on the re-call. Mutation-checked, one mutation per run: Reopen returning false reddened all three window tests and the first two model tests (run 20261020-223830.837-4004059199); canRoll without the recallable clause reddened only the Roll-button assertion (run 20261020-224001.392-1041813040); dropping the won-row refusal reddened only the won-row test (the run after it); dropping the phase guard stayed GREEN (run 20261020-224117.286-274814919), because the fourth test's selecting case used a pending row and never reached the guard -- it now resolves a row during selection and asserts the refusal, and that version went red under the same mutation before the guard was restored.
  • docs/20 and docs/04 record the ending; the What's New popup and the CurseForge page gain the Fixed bullet.

Fixed -- Might of Cenarius read as a caster ring after ItemDB v1.6.1

ItemDB v1.6.1 stores an on-equip physical-damage aura as ITEM_MOD_PHYSICAL_DAMAGE_DONE_SHORT instead of spell damage (Peer Review, thread ac7aaba1). Dibs knew neither spelling as physical, so 21189 (that plus stamina) fell into both gear camps and was still offered to casters. The key is now in Items.ItemCamps' PHYS_STAT, and Items.StatCategory files it under physical rather than other. Scoring is unchanged: ItemDB ships no weight for it (read in its Scoring/Vanilla.lua and Scoring/TBC.lua vocabulary comments). attack_spec +1 (the ring's stats read as physical only) and a source_spec assertion; each went red with its line broken.

  • The same release's armour-ignore key (ITEM_MOD_IGNORE_ARMOR, TBC only, e.g. 30898; Peer Review, thread a4b00494) was spell penetration before, so StatCategory filed it under spell and now under other. Both tables above now take it as physical. attack_spec +1 and a source_spec assertion, each red with its line broken. ItemDB ships no weight for it either: its tools/build-bis-weights.py leaves HawsJon's ArmorPenetration unmapped because the unit is not verified to be flat armour.
  • The Planner's stat block no longer shows a tooltip sentence as a stat name. Its statLabel took any WoW global for the key, and ITEM_MOD_SPELL_DAMAGE_DONE's is "Increases damage done by magical spells and effects by up to %s.", shown with the raw %s wherever spell damage has no weight-list label. It now calls statGlobal, the one rule every other stat consumer shares (Peer Review, thread a4b00494): the client's _SHORT label first, then the bare global, and never a % format string. So the row reads the localized "Bonus Damage" instead of the key's munged English. plannerdraw_spec +1, the first spec to drive the stat block (PlannerUI:RefreshSummary over a stubbed Loadout); it went red with statLabel put back to its own copy of the rule.
  • Weapon DPS was filed under "other" in the Planner's stat groups. Items.StatCategory had no pattern for ITEM_MOD_DAMAGE_PER_SECOND_SHORT, though ItemCamps has always counted it physical. Found by the new agreement check below on its first run; it now reads physical. That category also feeds Buffs:OffensiveEP, the base a "+% damage" buff is scaled by, which would have started counting every weapon's DPS summed into one key and priced at the main-hand weight (live: isStatSlot admits the weapon slots, Peer Review thread 66537cdd).
  • A "+% damage" buff now counts your weapons, each at its own hand's weight. Until now Buffs:OffensiveEP left weapon DPS out entirely, so Sayge's +10% and the like were under-valued for melee and hunters. The operator, 2026-10-18: "we need to calc it for mele dps, otherwise we're not accurate". The summed DPS key is skipped (OFFENSIVE_SKIP) and each of the main-hand, off-hand and ranged slots is scored with ScoreStats at the SLOT's equip location, so a one-hander in the off hand is priced as an off-hand swing. Which slot is the off hand comes from Items.HandOf, the rule GearEP already uses, rather than a second table (Peer Review L1), and the spec loads the real HandOf. buffs_spec +2; scoring the summed key, pricing by the item's own type, or ignoring HandOf each turned the per-hand test red alone. Known costs: on the library's older fallback path (no DB.ScoreStats) SumStatEP reads the raw scale, whose rows key weapon DPS as DPS_MAINHAND/DPS_OFFHAND/DPS_RANGED and never as the stat key (ItemDB Data/Vanilla/_core/BiSWeights.lua), so there the weapon term is 0 and the buff scores as it did before; and the per-hand read takes the item's raw stats without enchants, which loses nothing today because no enchant source emits the DPS key (the tooltip parser in Data/Enchants.lua, the mp5-only override in Modules/Loadout.lua, and ProfessionDB, which contains no DPS key; Peer Review L2).
  • The two stat classifiers are checked against each other (Peer Review, thread f6d96734). ItemCamps' magic/physical tables and StatCategory's pattern chain split the same keys, and both ItemDB respells above needed an edit in each. They are not one table (DEFENSE is a physical camp and a "defense" category), so attack_spec now walks the real tables, exposed read-only as Items.CAMP_STATS, and asserts every magic key is "spell" and every physical key is physical, primary or defense. Floors on both table sizes keep an empty read from passing.

Fixed -- other guilds' raids showed up in your Raids tab and attendance

Discord (GUNNEY), 2026-10-17: "Dibs sees raids from any guild that uses the platform on the server not just the guild that you are in". Confirmed by reading and reproduced in a spec. The raid type is admitted on the group leg as well as the guild leg, because a pug sees its own night through the record. RaidSync's Apply stored every record a peer sent, and no record said which guild it belonged to. So one pug with a member of another guild handed their whole archive across, the guild leg spread it to every guildmate, and it showed in Raids > Live and History and counted in Attendance. I built the raid sync without a guild check.

  • The record carries its author's guild, with its realm. Raid.New takes guild (RaidController:Start passes Raid.PlayerGuild()). It is outside the canon, travels in Raid.Wire, is kept by Raid.Accept from the first copy that had it and never overwritten, and an older copy can fill it in when the held one has none. The stamp is "Name-Realm" (Peer Review G2, thread 3624f9e7): GetGuildInfo's fourth return is the guild's realm, nil for the player's own (warcraft.wiki.gg API_GetGuildInfo; Blizzard's Era source reads only three returns), so a bare name would have let a same-named guild on a connected realm through. New pure Raid.GuildKey(name, realm, home) builds it by the rule Import.ReadGRMAlts uses for GRM's key; Raid.PlayerGuild() is the one live reader, used by the stamp, the admit context and the character snapshot.
  • The gate is on the record, not the sender (Peer Review, thread 71990bcc). Restricting the type to the guild would cut pugs off their own night. New Raid.Admits(r, ctx) keeps a record when one of this account's characters led it or is named on it (snapshot, either kind of amend, or bench), when it is stamped with a guild one of the account's characters is in, or when its author is a current guildmate. The last rule keeps every record from before the stamp, and a guild's archive across a rename. RaidSync.New takes admits; Apply skips what it refuses and counts it as foreign in Health(). RaidController:Register passes RaidController.Admits.
  • A record whose page is at least half current guildmates is also kept (Peer Review G1, HIGH). With only the author rule, every pre-v0.8.1 night an officer led was deleted once that officer left the guild, on every client not named on it, while clients that attended kept it, so Attendance totals split between clients. I missed that in the first cut. The page tells our night from another guild's where the author cannot. Raid.Admits now also returns which rule kept the record (mine, guild, members, guildmate). The page is the attendee set (Raid.AttendeeSet: snapshot plus an officer's amend-in, minus amend-out), so a night an officer built by amend is judged on who was in it (Peer Review thread 0da4dd8e, finding 1; my first cut counted the snapshot only).
  • The members rule applies to stamped records too (Peer Review R1, thread 0da4dd8e). After a guild rename the stamp names a guild nobody is in any more, so a stamped night whose leader had left was dropped on clients not named on it: G1's split again, and the stamp my G1 fix wrote is what shut the members rule off for those nights. A page mostly of current guildmates is ours whatever name it carries. It is checked before the author rule.
  • The prune stamps before it drops. PruneForeign stamps our guild onto an unstamped record it keeps for the members rule, so a later departure cannot reach it. Never on mine: a night one of our characters attended in another guild is not our guild's to stamp. Never on guildmate alone either (Peer Review R2): that is a night a guildmate led in their old guild, and my G1 cut stamped it ours, so it stayed for good after they left again and reached peers carrying our stamp. The stamp is local: it is outside the canon, the envelope stamp and Raid.EditDigest, so RaidSync.IndexLeaf does not change and nothing is re-offered. Each client stamps its own copy on its own prune.
  • Each character's guild is recorded (Characters snapshot guild, false once the character is in none), and new Characters.Account() returns this account's characters and their guilds. So a raid one of your alts was in, or one from the guild your main is in, is still yours when you log onto a character in a different guild.
  • The raids the leak already delivered are dropped. New RaidController.PruneForeign() removes stored records Admits refuses. It runs only once LibGuildRoster is ready and the character is in a guild, because before that "is the author a guildmate" is false for everybody and the guild's own pre-stamp archive would go. It is wired by RaidController.WirePrune() from Register and at PLAYER_ENTERING_WORLD, and runs on OnRosterReady. The settlement latches are kept, so a dropped record that ever comes back is not paid twice.
  • Tests/raidguild_spec.lua (new, 28 cases) drives the report through the real registered handlers: another guild's member holding three raids hands over only the one our character was in, it does not spread to a guildmate, our own guild's nights still travel, and with the gate removed all three arrive. It also pins the rule, the stamp, the controller's gate, the prune and its roster-ready guard, and the guild capture. Mutation-checked: seven production lines, each broken in turn, each turned its own test red. The G1/G2 round added 14 cases (42 in all): the members rule at, above and below half, on a stamped record and an empty page; the realm key; a leader who leaves after the prune stamped their nights; one who left before this build; and no stamp on a night we only attended. Six more mutations, each red on its own test. The third round (thread 0da4dd8e) added 5 (47 in all): a page built by amend-in, one emptied by amend-out, a stamped night after a rename (unit and end to end through the prune), and a guildmate's old-guild night left unstamped and dropped when they leave. Three more mutations (the snapshot in place of the attendee set, members back to unstamped-only, the prune stamping on guildmate), each red on its own tests.
  • Tests/attendancecommit_spec.lua's two synced clients (a leader and a master looter in one guild) had no guild roster, so the gate refused the leader's closed copy and four settlement cases went red. The fixture now gives them a roster that knows every OldBlanchy name, which is the guild night those cases describe. No assertion changed.
  • KNOWN COST: a refused raid is re-offered by its holder on every exchange, because we never hold its canon. The traffic is capped by Difference's per-response limit and only happens while grouped with another guild. The same re-offer is what heals a refusal made before the roster could answer.
  • KNOWN COST: a guildmate who joined from another guild brings the raids THEY led there, because "author is a current guildmate" cannot tell a renamed guild from a changed one. Raids other people led in their old guild stay out. Those nights are never stamped ours (R2), so they leave again when the guildmate does, and they pay nothing into our ledgers (Raid.PaysInto, below).
  • KNOWN COST: a character in no guild never prunes, because there is no roster to be ready. It keeps whatever the leak delivered before this build, though nothing new gets in.
  • KNOWN COST: logged onto an alt in a different guild, unstamped (pre-v0.8.1) raids led by your main's guildmates fail the guildmate rule and are pruned, then re-sync from your main's guild when you log the main back in. Stamped raids are kept through Characters.Account(). The members rule now keeps most of those too.
  • KNOWN COST: an unstamped night whose leader AND most of whose raiders have since left the guild is still dropped on clients not named on it. The prune's stamp stops this happening to any night it sees while those players are still guildmates.
  • KNOWN COST: another guild's stamped night that our raiders made up at least half of is kept, because the members rule now applies to stamped records (R1). Anyone of ours named on it kept it already.
  • KNOWN COST: a joint night split evenly between two guilds (Peer Review R3) passes the half threshold for both, so each guild's prune stamps its own and a third client keeps whichever copy reached it first. The members rule still keeps it on a client holding the other guild's stamp while half the page are its guildmates; once they have left, it drops there.
  • A night we may hold is not a night we pay (Peer Review finding 4, thread 08ddc59f). RaidController.Opened and Settle checked only their latches and the record's paid stamps, then handed every attendee to SRBonus:AwardRaid, DKP.OnRaidStart/OnRaidEnd and EPGP.OnRaidStart/OnRaidEnd. So another guild's closed, unsettled night that Admits kept (one of our characters on it, or since R1 half its page ours) was paid out of our ledgers to its attendees, strangers included, whenever our master looter's client swept it, and MarkSettled then recorded our officer as having paid it. New pure Raid.PaysInto(r, ctx): a stamped night pays here only when the stamp is this character's guild; an unstamped one when we led it, or its leader or most of its page are current guildmates. It answers nil while the guild name or roster has not loaded, and both callers then return WITHOUT latching, so a later sweep still pays our own night (since finding 1 below, a "no" is not latched either). RaidController.PaysInto is the live wrapper. Decided on purpose: a pug night one of our characters attended in another guild earns in that guild's ledgers, not ours, and R1's half-ours rule keeps a record but never pays one (a renamed guild's old nights were settled when they ran). Two follow-ups (Peer Review P1/P2): "in no guild" before the roster is ready also answers nil, because a "no" was then latched for good and login can report no guild for a moment before guild data arrives; and the roster's OnRosterReady callback now runs Sweep after PruneForeign, so a night that waited is paid then rather than on the next sync batch.
  • KNOWN COST: an unstamped night led by a stranger, from a build before the stamp, that this client sees before its roster loads is re-judged on every sweep until it does. Cheap, and it never pays wrongly.
  • "Not ours" is no longer latched (Peer Review finding 1, thread 78886663). The latches live in DibsDB, which is account-wide, and my first cut latched a firm "no": an alt in another guild sweeping the main's unsettled night first left it unpaid for the main too. The latch stops a second payment and a "no" pays nothing, so Opened and Settle now return unlatched on anything but a firm yes. Cost: one Raid.PaysInto call per held foreign record per sweep. Consequence, named rather than fixed: a character who later joins the guild that ran an unsettled night matches its stamp, and pays it then if no client has marked it settled -- consistent with "points go into the guild this character is in". WirePrune's already-ready path (a /reload) now sweeps as well as prunes, so both paths match (Peer Review nit).
  • raidguild_spec +2 (60): an alt in another guild sweeps the main's stamped night (nothing paid, nothing latched) and the main then pays it once over two sweeps; and a /reload with the roster already ready pays. Mutation-checked: the "not ours" latch put back in Opened, then in Settle, and the /reload Sweep removed, each red on its own test.
  • raidguild_spec +11 (58): five end-to-end cases through Sweep (Peer Review's half-ours foreign night as master looter, a pug night, our stamped night, a pre-stamp guildmate night, and one that waits for the roster and is paid by the ready callback) and six on the pure rule. Mutation-checked, each red on its own tests: the not-ours exit in Opened, the same in Settle, the can't-tell-yet exit, any stamp counting as ours, P1's guildless-before-ready line, and P2's Sweep in the callback. Finding 3's swap (author rule back before members) went red at :350 and :370, so no new spec was needed for it.
  • Two existing fixtures described nights this guild owns but could not show it: attendancecommit_spec's synced clients had a roster with no IsReady (added), and epgprosters_spec's roster-handoff case used a stranger's unstamped night (now this client's own). No assertion changed.
  • The bare-name stamp from the first cut never shipped, so no stored record carries one and nothing needs migrating.
  • Not covered: whether the other sync types (wishlists, soft reserves, the DKP and EPGP ledgers, the loot log, raid status, membership) carry the same leak shape. Only the DKP ledger declares guildOnly, and only while its setting is on. That sweep is the next item.

Fixed -- other guilds' DKP, EPGP and bonus points reached your standings

  • The same leak, in the points ledgers. Every ledger (DKP, EPGP EP and GP for every roster, SR+, lost rolls) rides the group leg, and LedgerSync's index path sends every subject a peer lacks -- so a pug with a member of another guild handed that guild's ledger across, and the guild leg spread it to every guildmate. Raid-kind entries were accepted from anyone (the layered gate accepts-and-tallies when no ML is live). Officer-kind entries were already safe: Comms isGuildOfficer answers from our own guild roster, so another guild's officer is refused.
  • Ledger Record now stamps the author's guild (Raid.PlayerGuild, "Name-Realm") outside the canon, like raid and loot; Accept keeps it. No canon change, and an older client drops the field.
  • LedgerSync.Admits gates every entry before the trust split, wired once in Bound:Register so all six ledger types get it. Stamped: kept when the stamp is one of this account's guilds. Unstamped: kept when its author or its subject is one of our characters or a current guildmate -- the subject too, so a guild's history survives on new clients after the master looter who wrote it leaves. Not loaded yet: refused as "roster" so the peer re-offers. A guildless account takes everything, as before -- it is the pug, it has no guild leg, and the master looter re-checks every bid against its own copy (DKPAuctionSync). Refusals count in Health().authorized.foreign and never mark the peer as failing.
  • KNOWN COSTS: a pug who is in another guild no longer sees their balance in the host's standings (their bids are unaffected); a pre-stamp entry about one of our members gets in through the subject rule; entries the leak already delivered stay, since an unstamped entry carries nothing that separates a departed officer's history from another guild's; a group member of another guild re-offers its entries each broadcast and we refuse them each time; and an account with characters in two guilds shows both guilds' points in one ledger. That last one comes from the ledger having no guild key (one store per ledger in the account-wide DibsDB), not from the gate: the alt in guild B takes B's entries over B's own guild leg anyway. Counting standings by stamp for the current guild would remove it; that is a separate design (Peer Review, thread 0ae76f81).
  • A stamp that is not ours falls through to the author rule (Peer Review R1, thread 0ae76f81), matching Raid.Admits' rename handling: after a guild rename the stamp names a guild nobody is in, and a member who joined after it refused the guild's whole stamped history while older clients kept it. Kept when its author is a current guildmate (so it waits for the roster); never by the subject rule, which would readmit another guild's awards to a shared character. KNOWN COST: a guildmate who was an officer in another guild brings that guild's stamped entries they wrote, the same as the raid gate's guild-hopper cost.
  • Stamped foreign entries are pruned once the roster is ready (Peer Review R2): LedgerSync.PruneStamped, called from RaidController.PruneForeign (roster-ready callback and login). At login a guilded character can briefly read as guildless, and a fresh install, whose account has captured no guild, took everything in that window and kept it. Peer Review asked for the guildless pass to wait on the roster's ready signal as Raid.PaysInto does. I did not do that: LibGuildRoster goes ready only on a non-empty roster (LibGuildRoster-1.0.lua stabilization needs total > 0), so a genuinely guildless pug would wait for ever and never see the host's standings. The pass stays, and the prune removes afterwards what it let in. It re-judges ONLY what the guildless pass let in (Peer Review R3): Admits answers true, "guildless" there, the authorizer carries that reason past the trust split, and Accept stores a local provisional flag outside the canon, never read from the wire. The first cut re-judged every stamped entry held, so once a renamed guild's old name left the account's guilds and its officer left, it deleted history that Accept had kept for good, on some clients and not others. A flagged entry the prune keeps has its flag cleared, so it is never re-judged again. Stamped entries only: an unstamped one the gate refuses may be our own guild's history from a departed officer, and dropping it on one client while others keep it would split the standings. KNOWN COSTS: an unstamped foreign entry taken in the login window stays, like the pre-build ones; and a guildless player keeps every guild's stamped points they picked up pugging, but their old pug balances vanish when they join a guild, because the first prune after that drops them; and an officer who undoes another guild's award during their own login moment leaves a points correction that nobody's history explains (every client agrees on it, measured in ledgerguild_spec).
  • ledgerguild_spec (new, 18): the pure rule, R1, the stamp through Record and Accept, a two-guild exchange through the real handlers, another guild's officer entry refused before the officer check runs, the live LedgerSync.AdmitContext with a foreign entry, the prune, the provisional flag through the real Apply, the flag cleared on what the prune keeps, and an undo of ours whose target is pruned leaving the balance a guildmate computes. Mutation-checked: the stamped-foreign refusal, the subject rule, the guildless pass, the stamp in Record, the stamp in Accept, the not-a-failure set, R1's author fallthrough, the prune's stamped-only condition and the raid prune's call into it, the flag set in Accept, the provisional-only re-judge, the flag clear, and the officer case with the gate moved after the officer check, each red. epgprosters_spec's two-client case now gives both clients a guild (they model guildmates); ledgersync_spec's zero-tally case includes foreign.

Fixed -- other guilds' loot awards reached your Loot Log

  • The same leak, in the loot log (Peer Review a9f81c3c, thread 71990bcc). lootlog rides the group leg, Provide sends every canon a peer lacks and Apply handed each to LootLog.Accept with no guild check. Two costs: another guild's award to a name that matches ours marked a prior win (WonBefore, the "you already won this" marker), and a canon-less award (from before raid canons) could deny an SR+ plus through SRWinnersInRaid's raid-name fallback. A canon-stamped one could not, since another guild's raid canon never matches our night's.
  • One rule, not a third copy. LedgerSync.Admits' entry rule is lifted into LedgerSync.AdmitsBy(stamp, author, subject, ctx); Admits calls it with the entry's subject and new LootLog.Admits with the award's winner. So R1's not-ours-stamp branch (author only) is the loot log's too. A legacy row was minted with no author, so only its winner can admit it. A disposal's winner is a sentinel nobody is, so its recipient (to) is its subject (Peer Review 8e9efea0, thread 29567ab5): otherwise a pre-stamp disposal by an officer who has since left could never get in on a new member's client. It reopens no leak, since a disposal is never a prior win or an EPGP charge, and a stamp that is not ours still goes to the author rule alone. AdmitsBy no longer builds a throwaway table per call.
  • LootLog.Record stamps the author's guild outside the canon, Wire carries it, Accept keeps it. Accept(e, ctx) judges after "known", as Ledger does; Apply builds the context once per batch through LedgerSync.AdmitContext and counts refusals in SyncHealth().foreign, never as a rejected peer.
  • R3 carried across: an award let in by the guildless pass is stored provisional (local, off the wire), and LootLog.PruneStamped, called from RaidController.PruneForeign beside the ledgers' prune, drops the stamped foreign ones once the roster can answer and clears the flag on the rest. It swaps in a new entries table rather than removing in place, because All memoises its sort on the table and its count: an in-place removal followed by an append that restored the count served the stale sort with the dropped award still in it.
  • KNOWN COSTS: an unstamped row (from before this build) whose author and winner, or recipient, have both left the guild is refused by members who join later, and holders keep re-offering it, so the Loot Log differs between old and new members -- the ledgers' unstamped shape; an unstamped award to one of our characters from another guild gets in through the winner rule, as a pre-stamp ledger entry does; awards the leak delivered before this build stay; a forfeit authored by a different officer than the win it cancels may carry a different stamp, so one can be refused without the other (not verified in play): a loot award that another guild's officer later rolled off can still show as won (Peer Review 2a49cad9; the reverse, a forfeit kept without its win, is harmless). From the ledger work (Peer Review finding 3, thread 2f593d94): an undo of ours whose target was another guild's pruned award leaves our -10 standing on every client with nothing to cancel, so the balance is convergent but 10 low everywhere.
  • lootlogguild_spec (new, 15): the rule incl. the legacy and disposal cases (a disposal through its recipient, refused when the recipient is a stranger, and still refused on another guild's stamp), the stamp through Record, Wire and Accept, a two-guild exchange through the real Apply, the flag and the prune, the stale sort, re-refusal of a pruned canon, and the live PruneForeign path. Mutation-checked, ten lines each red alone. nocaller_spec's pooled ceiling goes 110 -> 112 because the two PruneStamped exports now share a short name.

Fixed -- other guilds' wishlists showed as competitors

  • The same leak, in the wishlists (Peer Review thread 71990bcc). The index answer sends every list a peer holds, so a pug member from another guild handed over their guildmates' lists -- people in neither our group nor our guild -- and the guild leg spread them. They showed in "who else called dibs" and on the Wishlist Loot board, and because a gated Wishlist Loot roll admits only its competitors (LootSession), an item only an absent stranger had listed gated a roll nobody present could enter.
  • The gate is on the owner. Apply keeps a list only when its owner passes Comms:IsTrustedSender -- a guildmate or a member of the current group, the rule the transport already states for a sender -- and counts refusals in WishlistSync.health.foreign. A pug member's own list is kept, and so is a guildmate's list relayed by anyone. The store is session memory, so nothing needs pruning.
  • A refusal is reported back. The index request lists each refused owner at the stamp refused, so the holder's MissingOwners skips it until it changes. Without that, a holder with more than MAX_LISTS (8) refused lists sorted ahead of ones we would keep re-sent the same refused eight every round, and ours never arrived. Each refused owner is re-judged on every request and dropped from the report once it passes (Peer Review W1): my first cut kept reporting a refusal made before the guild roster loaded, or before a stranger joined the group, so the holder's strictly-newer test never sent that list again until its owner edited it.
  • KNOWN COSTS: before the guild roster loads, a guildmate's list is refused and is taken on the first exchange after it loads; a stranger's list taken while they were in the group stays for the session after they leave; the refused set grows by one owner per foreign list seen in a session and rides every index request. Not fixed here: a GUILDMATE who is not in the raid but listed the item is still a competitor, so the same empty gate could happen with our own absent members. That is not a guild leak; it is fixed in the section below (Wishlist Loot held a roll for a guildmate who was not in the raid).
  • wishlistsync_spec +2: a two-guild index exchange through the real Provide and Apply, and a guildmate refused before the roster loaded and taken after. Mutation-checked: Apply merging without the gate, the request leaving refused owners out, and the request never re-judging them, each red.
  • The gate moved into its own module the same day, when the soft-reserve types needed the identical mechanism: see the next section. The first cut's WishlistSync.Admits, its refused map and its liveTrust are gone; WishlistSync now takes Modules\OwnerGate.lua's gate, and the pure rule's cases moved to ownergate_spec.

Fixed -- Wishlist Loot held a roll for a guildmate who was not in the raid

  • The case the section above left open (todo #2). The sync holds every guildmate's list, present or not, and Comms:WishlistRosters returned all of them. So under Wishlist Loot an item only an ABSENT guildmate had listed gated a roll on that one name, nobody present could enter it, and the countdown burned -- the failure WishlistLoot.Strategy falls open to avoid, reached through a non-empty set of people who were not there. They also showed in the raider prompt's "who else called dibs" and on the board. I wrote the rosters without asking who was present.
  • Comms:WishlistRosters narrows to the group when grouped (the group set Comms:InSameGroup uses, built once per call), and returns every list solo, so the board still shows the guild's wishlists while a raid is organised. Narrowed at the one source rather than in a consumer, so the roll routing (LootMethods.Env), the raider prompt (GUI/LootRoll.lua) and the board (GUI/WishlistLoot.lua) cannot disagree. This also retires the section above's cost for rolls: a stranger's list kept after they left the group no longer reaches a grouped roll.
  • The Master Loot window now takes who wishlisted the item when the roll OPENS (Peer Review N1 on thread e2ca6168). I first wrote that its loss categorising was unchanged, because losers are in the group; but it read the rosters at AWARD time, so a loser who rolled and then left the group before the award was no longer in them, and their loss stopped counting as wishlisted. activeWishlisters is stamped beside activeItemID at the five roll starts (the auction start clears it: an auction award counts no losses) and cleared with the other actives. The award takes the UNION of that set and a live read (Peer Review, thread e2c37eaf finding 2), so a raider who wishlisted the item or joined mid-roll counts too; it over-counts only one who removed the item mid-roll.
  • New wishlistpresence_spec (4): an absent guildmate's and a raidmate's lists delivered to the master looter through the real index exchange, on the real Comms; the absent-only item falls open, the shared item gates on the raidmate alone, and solo keeps every list. Mutation-checked: returning every list while grouped turned the three grouped cases red, and narrowing solo turned the solo case red. docs/23-wishlist-loot.md says which set it is.
  • wishlistpresence_spec +1: LootMethods.Env on the real Comms carries the raid's lists and not the absent guildmate's (the consumer specs all stub WishlistRosters, so none of them would see a consumer re-widen the set); Env reading the sync's unfiltered store turned it red. lostrunview_spec +1: Ana wishlists, rolls, leaves, Bob's roll awards, and her loss is still wishlisted; reading the rosters at award time again turned it red, alone. lostrunview_spec +1: Ana adds the item to her wishlist mid-roll and her loss is wishlisted; the snapshot alone at award turned it red, alone.

Fixed -- an alt in another guild hid that guild's regulars from your standings

  • The cause (todo #2, Peer Review thread 40c821e9). membership.guildSeen is account-wide and keyed by name, and ReconcileRoster adds every roster name to it. So an alt in guild B put guild B's roster there; on the main in guild A the reconcile then archived every guild B member with guild A history (they are not in A's roster), and StatusOf hid them from A's standings, or read an unarchived one as an A guildmate. On the alt the reverse ran: every guild A member with history was archived, then restored by the main's next login, every time.
  • The two local decisions now leave out another guild's names. foreignOnly in Modules/Characters.lua is a name this client filed only under another guild's seenIn stamp and never under the current one; the reconcile's everGuildmate and StatusOf's guild test both skip it. A name in no seenIn set (learnt from a guildmate's push) is kept. The wire stays raw, by Peer Review's ruling: hashes, leaf, SeenKeys, Provide and Request are on the unfiltered union, since a filtered Provide against a raw leaf would leave a peer asking for ever.
  • A roster read that cannot be put to a guild now does nothing (Peer Review M1, thread cb16d3d7). My first cut let a pass with no guild stamp yet (GetGuildInfo not answering at the ready edge) exclude nothing, so it archived every guild B regular with A history, and every later pass then saw them as foreign-only and never restored them: the bug made permanent by one early login. ReconcileRoster now returns before learning or reconciling when rosterGuild is nil, which covers both the unloaded stamp and a roster that disagrees with it. That also stops a disagreeing read (the guild just left) being learnt into guildSeen and pushed to the new guild. The next agreeing pass, OnRosterCompleted or the coalesced reconcile, does the work (not traced against a real login's event order). KNOWN COST: LibGuildRoster clears its cached home key only when it sees us guildless, so a leave-and-join with no guildless roster update between them leaves every pass that session unattributable, and no leaver is archived until a reload (filed to Peer Review, thread df49d3e9).
  • MigrateAll remaps the per-guild seenIn sets too, alongside guildSeen, so a bare entry folded to its full key in one set cannot stop matching it in the other and open the filter for that name. Whether a bare name can reach seenIn at all was not established; the remap closes it either way. One case in Tests/charmigrate_spec.lua, red with the remap disabled.
  • KNOWN COSTS. Polluted names already pushed still reach guildmates, who still judge them by the union; a name a guildmate's alt learnt arrives peer-learnt and is trusted. seenIn started empty at this version, so names an alt put in the union before upgrading are not filterable until that alt reconciles again. A name archived before this fix stays archived until it reappears in our roster.
  • New membershipguild_spec (6) on the real Characters and Raid: the alt's guild B read, then the main's guild A reconcile, keeps guild B's regular unarchived and in the standings; the alt does not archive guild A's members; our own leaver and a peer-learnt name still archive; a name in both guilds' sets is ours; a pass before the guild name loads archives and learns nothing, and the loaded pass after it leaves guild B's regular alone. authortrust_spec's disagreeing-read case now asserts the union is NOT fed (it pinned the leak), and membership_spec's fixtures gained the loaded stamp an in-guild client has. Mutation-checked: the reconcile on the raw union, StatusOf on the raw sets, and the reconcile without the unattributable-read guard each turned their cases red.

Fixed -- other guilds' soft reserves, Main/Alt declarations and unlock requests reached you

  • The same leak, in three soft-reserve types (Peer Review thread 71990bcc). sr_reserve, sr_declare and sr_request are member-owned, ride the group leg, and each index answer sends every record held. So a pug member from another guild handed over their guildmates' reserves (which SoftResSync.Distribute then wrote into our raid's saved reserve list), their Main/Alt declarations (into the officer view and the roll tier), and their pending unlock requests (into our officers' Requests queue), and the guild leg spread all three.
  • One gate for every member-owned type: new Modules\OwnerGate.lua, loaded after Comms and before WishlistSync in all three TOCs. OwnerGate.Admits(owner, trusted) is the rule; the live rule, OwnerGate.LiveTrust, is a guildmate, a member of the current group (Comms:IsTrustedSender), or an officer-designated regular raider outside the guild (Comms:IsGuildPeer, decision 36); no live rule admits all. The regular-raider half came from Peer Review 750fffbb (thread 917bd1ae): my first cut used IsTrustedSender alone, so a regular raider was ours when they sent and a stranger when they owned a record, and their wishlist, reserves, declaration and unlock requests stopped reaching guildmates and officers not grouped with them. A designation that lands after a refusal heals on the next index request. OwnerGate.New() gives each type its own refused keys: Take(key, owner, at, trusted) records a refusal at the newest stamp refused, and Report(have, trusted) re-judges every refusal, forgetting one that now passes and reporting one that still fails at its stamp. That is exactly the wishlist's mechanism (W1 included), factored out rather than copied three more times.
  • Who owns what: a reserve by its owner, a declaration by its character key, a request by from, the character that raised it (Requests.Submit refuses a request without one). A requester always takes back their own request, so a guildless pug still receives the officer's approval of their unlock.
  • sr_setup is not gated by owner. It is already accepted only when authored by a guild officer or the current loot leader (authoredByLeadership), and a setup our loot leader wrote is what a pug raider should take; a guildmate later judges that author by the same rule and refuses it.
  • KNOWN COSTS: the wishlist's three, per type. And reserves already written into a raid's saved reserve list before this fix stay there: a pug member's reserves for a raid they were in are indistinguishable from a stranger's once both have left, so nothing is pruned. They are no longer re-served to anyone, because the index is built from the session's peer copies, not from the saved list (reserveStamps). An unlock request from a player outside the guild who is not a regular raider only reaches officers in the same group as them.
  • Unlock requests already saved ARE pruned (Peer Review R1, thread 71990bcc): the request store is saved and account-wide, and the sync serves straight from it, so other guilds' requests that arrived before the gate sat in the officers' Pending queue for good and were offered to every peer. New Requests.PruneForeign(mine, trusted) drops every stored request whose from fails the owner gate's live rule, never one raised by any character on this account. It runs from RaidController.PruneForeign, so only once the guild roster can answer (before that every guildmate would read as foreign). KNOWN COST: a pug member's request is dropped once they have left the group; their own client still holds it and offers it again when they regroup. A guildless character never prunes.
  • New ownergate_spec, and requestsguild_spec for the prune. wishlistsync_spec +1: a designated non-guild, ungrouped raider's list through the real index exchange, refused before the designation and taken after, on the real Comms:IsGuildPeer. srdiscovery_spec +4: a two-guild case each for reserves, declarations and requests through the real index exchange (kept, refused, not re-sent, taken once the stranger joins), and the requester taking back their own approval. Its request fixtures now carry from, as every real request does. Four specs (declstamp, srdiscovery, wishlistindex, wishlistsync) load the new module ahead of the sync module they test. Mutation-checked: each Apply without its gate, Report never adding a refusal, Report never re-judging, dropping the own-request bypass, LiveTrust without its regular-raider half, the prune never being called, and the prune dropping this account's own requests, each red. nocaller_spec's pooled-unclassified ceiling stays at 112: Requests.PruneForeign shares a short name with RaidController.PruneForeign, so a call through a local alias made the sweep blind to it; the call is written by its full name instead (Peer Review nit c6d6ee24).

Fixed -- another guild's DKP rotation, officer unlocks and links, and settings reached you

  • Three more types with the same leak (Peer Review thread 71990bcc). dkp_econ (the DKP round-robin remainder cursor) adopted any newer cursor from any sender, so another guild's master looter in a pug overwrote ours and we re-served it. decl_lock was a plain union from every sender, so another guild's officer could mint an UNLOCK or a LINK naming one of our guildmates, and a link decides whose points an alt earns. And the settings push gate admits the current loot leader, which in a pug can be another guild's master looter, so Settings.ApplyPush replaced our whole stored configuration (DKP and EPGP economy, raid status ranks, loot overrides) with theirs.
  • Each is now judged on its AUTHOR, not the sender: DKP.EconAdmits on the cursor's by; Declaration.LockAdmits on a LOCK's subject under the owner rule and on an UNLOCK's or LINK's by under the author rule; Settings.PushAdmits on the stamp's by (the sender for an unstamped push). This account's own characters always pass. A guildless character keeps the pug boundary for the cursor and the lock log, and no extra gate for the push. decl_lock refusals are remembered and re-judged on every request, the wishlist's rule; a refused cursor heals with no bookkeeping, because our advertised record is unchanged and the holder re-offers it. New health counters: Declaration._lockHealth.foreign and Settings._pushHealth.foreign.
  • Raid status and the guild membership push, the last two types swept. Raid status has no sync type of its own; it rides the settings push (raidStatus), adopted only after PushAdmits. Tests/settingsguild_spec.lua now pins it on the real RaidStatus: another guild's push neither ungates nor gates a raid of ours, and our officer's replaces the rules. Its first draft passed with the gate removed, refused as stale by the stamp its own fixture minted, so its pushes are stamped after the local one. Each of its three state lines was then mutated on its own (Peer Review finding 3, thread 40c821e9): Adopt merging, the foreign rules adopted before the gate with and without that merge, each red on its own line. The membership push already accepts names only from a sender in our guild and raider designations only from an officer, so it has no group-leg leak. The alt-in-another- guild path into the standings union is a separate todo.
  • What was already held is pruned: DKP.PruneForeignEcon resets a foreign cursor to the unstamped default and Declaration.PruneForeignLocks drops foreign lock records (and refuses to run without both rules, so a missing module cannot empty the log). Both run from RaidController.PruneForeign, called by full name.
  • ONE author rule, OwnerGate.AuthorTrust (Peer Review findings 1 and 4, thread 97f5c681). My first cut gave the three gates three spellings of "is this one of ours", each on the LIVE roster, and that was wrong in a way that does not heal: an officer who linked an alt and then left the guild read as foreign, and the prune deleted their links and unlocks on every client, so no peer kept a copy to re-offer. The rule is now: in the live roster NOW, or EVER seen in THIS guild's roster by this client (Characters.WasGuildmateOf, keyed by the same normalized Name-Realm as rec.by). opts.raiders also admits a designated regular raider, which the cursor takes and an officer's unlock does not. Guildless, it is the pug boundary. With no comms layer it is nil and EVERY caller refuses: one nil convention, where before the cursor and the lock log refused and the push admitted. A LOCK's owner passes on the owner rule or on this one, so a member who left keeps their lock if they come back.
  • "Ever ours" is per guild (Peer Review X1, HIGH, thread 3edcb2a7). My first cut of the rule above read the guildSeen union, which is account-wide (DibsDB) and keyed by name alone. An alt in another guild put that guild's whole roster in it, so on the main the rule admitted the other guild's officers' links and unlocks, their master looter's cursor and their loot leader's settings push, and the prune kept them. The membership push then carried those names to our guildmates, whose rule admitted them too. New seenIn set in Characters' membership table, { [guild stamp] = { [name] = true } }, written only by ReconcileRoster and OnMemberJoined under Raid.PlayerGuild(), never from a peer; Characters.WasGuildmateOf(name, guild) reads it and AuthorTrust now calls it. guildSeen and the standings are unchanged. Characters.WasGuildmate has no production caller again and is back in nocaller_spec's SUPERSEDED table and backlog, where it was before this release.
  • A refused settings push is held and retried (Peer Review finding 2, thread 3edcb2a7). The push is one-shot, and at login or on a fresh install our own loot leader is not yet in the roster or the per-guild set, so it was refused and nothing re-sent it. Settings.ApplyPush now holds refused pushes in Settings._pendingPush, and Settings.RejudgePending retries them from RaidController.WirePrune's OnRosterReady and OnRosterCompleted callbacks. ONE PER AUTHOR (Peer Review N1): my first cut held a single slot, newest wins, so another guild's push arriving after ours replaced it and the retry refused theirs again, losing ours. Now the newest per author, at most Settings.PENDING_PUSH_CAP (8) authors, a full table keeping the ones it holds; a taken push clears only its own author's copy. A retry no longer adds to _pushHealth.foreign, which counts refused pushes, not attempts.
  • Requests and locks stay judged differently (Peer Review finding 3): an unlock request is dropped when its owner leaves, a lock is kept. Both rules are named in docs/06 so neither gets changed to match the other.
  • The prune waits for a COMPLETE roster (Peer Review G1, thread 97f5c681). RaidController.PruneForeign guarded only IsReady and IsInGuild, and LibGuildRoster goes ready on a SHORT login build, where a current officer missing from it reads as foreign. It now returns early unless IsRosterComplete, the idiom DKP already used, and WirePrune registers OnRosterCompleted so a repaired build prunes then. This covers the raid prune and the ledger, loot log and request prunes too, not only the three new ones.
  • What was held before this build is grandfathered (Peer Review efcf6bd5, HIGH, thread 63e1ccb1). seenIn is new and starts empty, so an officer who left before the upgrade was in it on NO client, and the first complete-roster prune deleted their links and unlocks guild-wide. That was a regression: v0.8.0 has no lock prune and keeps them. New Declaration.GrandfatherLocks, called once from Dibs.lua's ADDON_LOADED before any sync can deliver a record (flag declLockGate), records every lock-log canon then held in declLockHeld, and PruneForeignLocks skips those. Everything that arrives later is judged on arrival and by the prune. Not seeded from guildSeen, which takes names from guildmates' pushes and would bring X1 back. THE DKP ROTATION CURSOR TOO (Peer Review E1, thread e0bc7448): v0.8.0 has no econ prune, and a cursor last moved by a master looter who left before the upgrade was reset to the start on every client, none able to re-adopt a peer's copy (same author, not newer). New DKP.GrandfatherEcon, called next to it (flag dkpEconGate), records the held cursor's by and updatedAt in dkpEconHeld, and PruneForeignEcon leaves the cursor alone while it still carries that stamp; the next split replaces it and is judged as usual. Checked the other three prunes against v0.8.0 the same way: none of LedgerSync.PruneStamped, LootLog.PruneStamped or Requests.PruneForeign exists in v0.8.0, but the first two re-judge only entries flagged provisional, a flag this build adds, and none of the three reads the per-guild "ever" set, so none has this upgrade problem. Requests.PruneForeign does drop a pending request from someone who has since left, which v0.8.0 kept; that is the request rule docs/06 states, not this defect.
  • Reset all no longer opens the grandfather gates (Peer Review, thread e0bc7448). Wipe.All empties every key, the two gate flags included, and the session goes on without a reload, so whatever sync delivered after a wipe was recorded as held-before-the-gate on the next load and exempt from the prune for good, a pug member's lock included. Wipe.All now runs both grandfather functions on the emptied store, which records nothing and sets the flags. Wipe.IsEmpty and the Settings wipe prompt's count skip those keys (Wipe.GATE_KEYS), so the button still says there is nothing to wipe straight after a wipe. Tests/wipe_spec.lua runs both grandfather functions on a store holding a lock record and a stamped cursor and asserts every key they add is in Wipe.GATE_KEYS, which pins dkpEconHeld, the one entry an empty store never writes (Peer Review, thread 8432cf84 finding 2).
  • A roster read is filed only under the guild it belongs to (Peer Review N2, thread 3edcb2a7). ReconcileRoster read the library's roster and a fresh GetGuildInfo separately, and LibGuildRoster caches its home key until it sees us guildless, so a leave-and-join between two roster updates could file one guild's roster under the other's stamp. It now files nothing unless the library's home key names the same guild, compared as the WHOLE key rebuilt the library's way (faction, "-", guild name), not by suffix, so a guild whose name merely ends in "-" plus ours cannot pass (Peer Review finding 6, thread e0bc7448). And reading LibGuildRoster for that turned up a real one: the library fires OnMemberJoined and OnMemberLeft for a SISTER guild's roster too (SetSisterRoster, keyed by that guild), and RegisterMembership discarded the key, so a sister guild's joiner went into guildSeen, the per-guild set and our membership push, and its leaver was archived. Both handlers now act only on the home key.
  • The roster is reconciled before the prune reads it (Peer Review finding 4, thread 63e1ccb1). ReconcileRoster now also runs on OnRosterCompleted, and WirePrune reconciles ahead of its own prune on both edges, because CallbackHandler dispatches with next and Characters' handler could otherwise run after the prune.
  • The prune also waits for the guild name (finding 3): PruneForeign returns 0 while Raid.PlayerGuild() is not a string, since the per-guild set is keyed by it and every ex-member would read as foreign.
  • KNOWN COSTS: a member who left before v0.8.1 was installed is "ever ours" on no client, so a NEW link or unlock they authored is refused; what was already held when v0.8.1 first loaded is kept (above). Another guild's record that leaked in under v0.8.0 stays on the clients that already held it, as it does in v0.8.0, and so does another guild's DKP cursor until our next split. A settings push from a ninth foreign author is not held once PENDING_PUSH_CAP authors are, so our officer's push arriving after eight strangers' is refused and not retried (judged unrealistic, not measured). If the guild name loads only after both roster edges, nothing is pruned that session. A character who left us and now officers another guild still passes the author rule. A forged author name passes every one of these gates; that is the anti-tamper question docs/06 leaves open. One of our members in another guild's pug does not take that master looter's roll settings (the rolls are still run and decided on the master looter's client). On a roster that never completes in a session, nothing is pruned that session.
  • New dkpeconguild_spec and settingsguild_spec; decllocksync_spec gains a two-guild describe (ex-officer link and unlock survive the prune and still serve, a never-ours author is still pruned). dkpecon_spec's Apply tests get a guild-peer Comms stub, and epgprosters_spec's settings-push fixture now makes the stamp's author a guildmate, which the gate judges. Mutation-checked: each Apply without its gate, the lock refusal never re-judged or never reported, the prune never called, AuthorTrust without its "ever" half, PruneForeign without the complete-roster guard, the OnRosterCompleted callback doing nothing, and PushAdmits admitting on a missing rule, each red; run ids are in each spec's header.
  • New authortrust_spec (6 cases, on the real Characters, Raid, OwnerGate and Declaration): an alt in guild B reconciles, the main in guild A refuses and prunes a guild B officer's link, guild A's ex-officer stays ours, a peer's union push adds nothing to the per-guild set, and the set answers false while the guild name is unknown. settingsguild_spec +3 (a held push applied once its author is known, a still-foreign one never applied, a taken push clearing the slot), dkpeconguild_spec +1 (both roster edges re-judge), decllocksync_spec +1 (an ex-member keeps their own lock, Peer Review finding 4). Three fixtures stub WasGuildmateOf in place of WasGuildmate. Mutation-checked: AuthorTrust back on WasGuildmate, ReconcileRoster not writing the per-guild set (its own run, since the first mutation masked it), the lock owner without its "or ever" half, the re-judge not applying, the ready edge not re-judging, and the loot_roll gate with no raid-authority branch, each red.
  • authortrust_spec +6 (both "ever seen" sets agree on the roster after a reconcile; an ex-officer's link held at upgrade survives a complete-roster prune, a post-upgrade foreign link is still pruned, the grandfathering runs once; a disagreeing home key files nothing; a sister guild's join and leave are ignored). settingsguild_spec +3 (a later foreign push does not displace ours, a retry is not recounted, the cap keeps what it holds), dkpeconguild_spec +2 (reconcile before prune on both edges; nothing pruned while the guild name is unknown), membership_spec +1 on the real CallbackHandler (a sister guild's events ignored) and its callback count 3 -> 4, bootstrap_spec +1 (ADDON_LOADED grandfathers). Mutation-checked: nine breaks, eight batched in one run and the bootstrap call in its own, each turning its own target red; run ids in the spec headers.
  • Thread e0bc7448: dkpeconguild_spec +4 (an ex-looter's cursor held at upgrade survives a complete-roster prune, a later foreign cursor is still reset, a second load records nothing, a fresh install gains only the flag), bootstrap_spec +1 (ADDON_LOADED hands the store to GrandfatherEcon), wipe_spec +1 on the real Declaration and DKP (a record synced after a wipe is not grandfathered, and the wiped store reads as empty), authortrust_spec +1 (a key that only ends in our guild's name files nothing; its fixture now sets the faction), settingsguild_spec +1 (two of our authors held: the newer configuration wins in either retry order). Mutation-checked: the econ skip, the bootstrap call, the wipe's econ gate and the suffix match, batched in one run with each red on its own target only, and the recency guard in its own run; run ids in the spec headers.

Fixed -- anyone in your group could pop a roll prompt on your screen

Found reading the push-only types for Peer Review thread 97f5c681, none of which turned out to store and re-serve another guild's data. But loot_roll's Apply had no sender check, though its sibling loot_roll_end has one. The push gate admits any officer or loot leader (a raid assistant too) and the directed leg anyone in the group, so any of them could put up a roll prompt for an item nobody was rolling, and with auto-roll on the client really /rolled and whispered an answer. Apply now takes a prompt only when Names.Same(sender, MasterLooterName()), like loot_roll_end, or, when there is no master looter, from a sender Comms:IsRaidAuthority passes (the raid or party leader, never an assistant). The second half is Peer Review finding 5 (thread 3edcb2a7): ML:RerollOwed, from the trade-timer menu, rolls an owed item off from the bags with no master-loot check, and after the raid has left master loot an ML-only gate refused every raider's prompt for it. KNOWN COST: a client that has not resolved the master looter yet after a /reload judges a mid-raid roll by the raid-authority rule instead. lootrollsynctie_spec +5 (a non-master-looter refused, an assistant refused with no master looter, the raid leader's re-roll taken with no master looter, the raid leader refused while a master looter is set, the master looter taken under another spelling); its and mlownprompt_spec's fixtures now name the master looter.

Changed -- the oldest part of v0.8.0 moved to the archive

The entries above took the live file to 121,163 characters against the 120,000 ceiling, so v0.8.0's last three sub-sections (the standings import, the stale EPGP doc lines, docs/26) moved to CHANGELOG_ARCHIVE.md, emphasis converted to that file's underscores. The Peer Review X1 and finding 2 and 5 entries took it over again (121,161), so the bench sub-section followed the same day; it has no single-asterisk emphasis to convert.

Fixed -- planning another character listed no gear for any slot

Operator, 2026-10-17: logged in on an alt, picked the main in the Build tab's character box, clicked the trinket slot, and got "No gear matches your filters for this slot". It happened on every slot.

Picking a character resets the spec box to "Default", which is stored as no spec. For your own character that means "follow the live player" and resolves through Items:PlayerClassSpec. For another character, PlannerUI:ApplyPlanContext passed the nil straight to Items:SetScoringContext. LibItemDB's GetBiSWeights(class, nil) then looked up c[nil] and got no weights, so RankItems returned an empty ranking for every slot. I wrote that call, and it never resolved "Default" for an alt.

  • New Items:PlanSpecFor(classID, preferred, source) (Data/ItemSources.lua) returns the character's own snapshotted spec when the scale carries it, otherwise the scale's default alias, otherwise the first spec it lists.
  • New PlannerUI.PlanScoreSpec(planSpec, char) (GUI/Planner.lua): a template picked in the spec box still wins; "Default" goes through PlanSpecFor. ApplyPlanContext now scores the alt with its result.
  • Tests/planspec_spec.lua (new, against the real LibItemDB) checks four things: an empty ranking under a nil spec, the resolver's three fallbacks, a populated ranking once the spec is resolved, and the Planner-side choice. Mutation-checked: when PlanSpecFor returns its input unchanged, the reported case fails.
  • The account's Weights-tab choice now applies to alts too (Peer Review, thread 82ccbe2e finding 1). The template choice is stored per class and account-wide, so a hunter set to Beast Mastery was scored as Beast Mastery when logged in and as its talent-detected spec when planned from another character. "Default" for an alt now goes through the new Weights:ScaleFor(classID, detectedSpec) (Modules/Weights.lua), the same template resolution Weights:Source now uses. A custom template is registered for the alt's class and scored on the dibs source (exposed as Weights.SOURCE instead of spelling the string a second time). Two new planspec_spec cases cover it; disabling the lookup turns both red.
  • Tests/plannerdraw_spec.lua gains a case that draws the real Planner, plans an alt with no spec picked and records what ApplyPlanContext passes to SetScoringContext (finding 2). Restoring the old line fails it with "the alt was scored with spec nil".
  • v0.5.1 claimed more than it fixed. Its known issue, quoted in Tests/altgear_spec.lua:3-5, said switching character "may then show 'no matches' for every slot", and v0.5.1 fixed only the worn-gear half. The nil spec was already there at the Dibs-v0.5.1 tag: setPlanChar cleared _planSpec (GUI/Planner.lua:964) and ApplyPlanContext passed it straight to SetScoringContext (:942). So the "no matches" half shipped unfixed from v0.5.1 through v0.8.0.

Changed -- a spec pins Badge of the Swarmguard for hunters

A Discord report said Badge of the Swarmguard did not show as a hunter trinket (inbox handover 9d744b63, from TOGProfessionMaster). The Planner ranks by EP, not by ItemDB's best-in-slot lists. On the shipped Vanilla data the badge scores above zero for every hunter spec through its armor-reduction use effect. It is in LibItemDB's hunter trinket ranking, and it is a row in the Planner's own Trinket 1 list, which is read after the name collapse, the hidden-item gate and the quality floor. The new Tests/swarmguard_spec.lua pins all three. The likeliest Dibs cause of the report is the alt-planning bug above, which emptied the trinket list along with every other slot; that the reporter was planning from an alt is not verified.

  • The Planner's slot-list builder moved out of PlannerUI:Draw to file level as PlannerUI.ItemsForSlot(slotDef, minQ), unchanged, so that spec reads the list a player sees rather than the library ranking under it (Peer Review, thread 82ccbe2e N2). Draw keeps a local alias. Mutation-checked: skipping the badge in that builder turns the new case red.
  • The custom-template case in Tests/planspec_spec.lua now writes the template straight into the class store instead of going through Weights:SaveCustom, which registers it itself and hid whether Weights:ScaleFor did (N1). Deleting the register inside ScaleFor now turns that case red.

Changed -- v0.6.0 moved to the archive

The live file was at 117,188 characters against the 120,000 ceiling, so the v0.6.0 section moved whole to CHANGELOG_ARCHIVE.md (Peer Review, thread 82ccbe2e). The unattributable-read entry above took the file to 120,294, so v0.8.0's unidentified-raid fix followed it. The stat-classifier entries above took it to 120,369, so v0.8.0's on-time / completion / interval EP entry followed that.

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

Changed -- release notes for v0.8.0

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

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

New -- Shaman tank, the fourth Shaman spec

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

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

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

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

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

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

New -- import Core Loot Manager's EPGP settings

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

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

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

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

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

New -- loot council voting, per item

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Tests -- raid controller line coverage to 100%

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

The oldest eleven sub-sections of v0.8.0 (the standings import, the stale EPGP doc lines and docs/26, moved 2026-10-18, the bench, moved later that day, the unidentified-raid fix, moved after it, the on-time / completion / interval EP entry, moved after that, the boss-kill EP fix, moved 2026-10-19, the tier-token pricing fix, moved 2026-10-20, the dead-raid-record fix, moved 2026-10-21, and EPGP per-boss EP and token prices, moved 2026-10-22), all of v0.7.1 (2026-10-17), v0.7.0 (2026-10-17), v0.6.0 (2026-10-17), 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.

This mod has no additional files