UltimateDibs-v0.8.2
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 doorVoidalready opens); a won row is refused, since undoing a win isVoid's job andVoidannounces. The window decides which winnerless rows qualify (recallable: not disenchanted, not banked -- the two endings where the item did go somewhere) andreopenIfRecallablealso clears the stale "No winner" from the cell. - Roll and DE stay on such a row, on a finished table too (
canRollnow admitsdoneand a recallable row), andRollRowandDisenchantRowreopen the row beforeGoTo/ 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.
OnRollNowrunsreopenIfRecallablefirst, so a picked raider's/rollfires and a live-rolling raider's MS / OS / Pass light again for the row (LiveRollrefuses a resolved row). The raider's status row says the master looter can call it again (loottable_status_norolls_raider) when theloot_table_resultarrives withkind = "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/rolleither 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) andloottableui_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:Reopenreturning false reddened all three window tests and the first two model tests (run 20261020-223830.837-4004059199);canRollwithout 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, soStatCategoryfiled it under spell and now under other. Both tables above now take it as physical.attack_spec+1 and asource_specassertion, each red with its line broken. ItemDB ships no weight for it either: itstools/build-bis-weights.pyleaves 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
statLabeltook any WoW global for the key, andITEM_MOD_SPELL_DAMAGE_DONE's is "Increases damage done by magical spells and effects by up to %s.", shown with the raw%swherever spell damage has no weight-list label. It now callsstatGlobal, the one rule every other stat consumer shares (Peer Review, thread a4b00494): the client's_SHORTlabel 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:RefreshSummaryover a stubbed Loadout); it went red withstatLabelput back to its own copy of the rule. - Weapon DPS was filed under "other" in the Planner's stat groups.
Items.StatCategoryhad no pattern forITEM_MOD_DAMAGE_PER_SECOND_SHORT, thoughItemCampshas always counted it physical. Found by the new agreement check below on its first run; it now reads physical. That category also feedsBuffs: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:isStatSlotadmits the weapon slots, Peer Review thread 66537cdd). - A "+% damage" buff now counts your weapons, each at its own hand's weight. Until now
Buffs:OffensiveEPleft 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 withScoreStatsat 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 fromItems.HandOf, the rule GearEP already uses, rather than a second table (Peer Review L1), and the spec loads the realHandOf.buffs_spec+2; scoring the summed key, pricing by the item's own type, or ignoringHandOfeach turned the per-hand test red alone. Known costs: on the library's older fallback path (noDB.ScoreStats)SumStatEPreads the raw scale, whose rows key weapon DPS asDPS_MAINHAND/DPS_OFFHAND/DPS_RANGEDand never as the stat key (ItemDBData/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 inData/Enchants.lua, the mp5-only override inModules/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 andStatCategory'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), soattack_specnow walks the real tables, exposed read-only asItems.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.Newtakesguild(RaidController:StartpassesRaid.PlayerGuild()). It is outside the canon, travels inRaid.Wire, is kept byRaid.Acceptfrom 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 pureRaid.GuildKey(name, realm, home)builds it by the ruleImport.ReadGRMAltsuses 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.Newtakesadmits; Apply skips what it refuses and counts it asforeigninHealth().RaidController:RegisterpassesRaidController.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.Admitsnow 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.
PruneForeignstamps our guild onto an unstamped record it keeps for themembersrule, so a later departure cannot reach it. Never onmine: a night one of our characters attended in another guild is not our guild's to stamp. Never onguildmatealone 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 andRaid.EditDigest, soRaidSync.IndexLeafdoes not change and nothing is re-offered. Each client stamps its own copy on its own prune. - Each character's guild is recorded (
Characterssnapshotguild,falseonce the character is in none), and newCharacters.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 recordsAdmitsrefuses. 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 byRaidController.WirePrune()fromRegisterand at PLAYER_ENTERING_WORLD, and runs onOnRosterReady. 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 onguildmate), 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.OpenedandSettlechecked only their latches and the record's paid stamps, then handed every attendee toSRBonus:AwardRaid,DKP.OnRaidStart/OnRaidEndandEPGP.OnRaidStart/OnRaidEnd. So another guild's closed, unsettled night thatAdmitskept (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, andMarkSettledthen recorded our officer as having paid it. New pureRaid.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.PaysIntois 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'sOnRosterReadycallback now runsSweepafterPruneForeign, 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, soOpenedandSettlenow return unlatched on anything but a firm yes. Cost: oneRaid.PaysIntocall 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 inOpened, then inSettle, and the /reload Sweep removed, each red on its own test.raidguild_spec+11 (58): five end-to-end cases throughSweep(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 inOpened, the same inSettle, the can't-tell-yet exit, any stamp counting as ours, P1's guildless-before-ready line, and P2'sSweepin 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 noIsReady(added), andepgprosters_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:CommsisGuildOfficeranswers from our own guild roster, so another guild's officer is refused. LedgerRecord now stamps the author's guild (Raid.PlayerGuild, "Name-Realm") outside the canon, likeraidandloot;Acceptkeeps it. No canon change, and an older client drops the field.LedgerSync.Admitsgates every entry before the trust split, wired once inBound:Registerso 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 inHealth().authorized.foreignand 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 fromRaidController.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 asRaid.PaysIntodoes. I did not do that: LibGuildRoster goes ready only on a non-empty roster (LibGuildRoster-1.0.luastabilization needstotal > 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):Admitsanswerstrue, "guildless"there, the authorizer carries that reason past the trust split, andAcceptstores a localprovisionalflag 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 thatAccepthad 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 inledgerguild_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 liveLedgerSync.AdmitContextwith a foreign entry, the prune, the provisional flag through the realApply, 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 inAccept, 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 includesforeign.
Fixed -- other guilds' loot awards reached your Loot Log
- The same leak, in the loot log (Peer Review a9f81c3c, thread 71990bcc).
lootlogrides the group leg, Provide sends every canon a peer lacks and Apply handed each toLootLog.Acceptwith 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 throughSRWinnersInRaid'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 intoLedgerSync.AdmitsBy(stamp, author, subject, ctx);Admitscalls it with the entry's subject and newLootLog.Admitswith 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.AdmitsByno longer builds a throwaway table per call. LootLog.Recordstamps the author's guild outside the canon,Wirecarries it,Acceptkeeps it.Accept(e, ctx)judges after "known", asLedgerdoes; Apply builds the context once per batch throughLedgerSync.AdmitContextand counts refusals inSyncHealth().foreign, never as a rejected peer.- R3 carried across: an award let in by the guildless pass is stored
provisional(local, off the wire), andLootLog.PruneStamped, called fromRaidController.PruneForeignbeside 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, becauseAllmemoises 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 livePruneForeignpath. Mutation-checked, ten lines each red alone.nocaller_spec's pooled ceiling goes 110 -> 112 because the twoPruneStampedexports 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 inWishlistSync.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
MissingOwnersskips it until it changes. Without that, a holder with more thanMAX_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 itsliveTrustare gone; WishlistSync now takesModules\OwnerGate.lua's gate, and the pure rule's cases moved toownergate_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:WishlistRostersreturned 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 failureWishlistLoot.Strategyfalls 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:WishlistRostersnarrows to the group when grouped (the group setComms:InSameGroupuses, 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.
activeWishlistersis stamped besideactiveItemIDat 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.mdsays which set it is. wishlistpresence_spec+1:LootMethods.Envon the real Comms carries the raid's lists and not the absent guildmate's (the consumer specs all stubWishlistRosters, 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.guildSeenis account-wide and keyed by name, andReconcileRosteradds 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), andStatusOfhid 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.
foreignOnlyinModules/Characters.luais a name this client filed only under another guild'sseenInstamp and never under the current one; the reconcile'severGuildmateandStatusOf's guild test both skip it. A name in noseenInset (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 (
GetGuildInfonot 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.ReconcileRosternow returns before learning or reconciling whenrosterGuildis 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 intoguildSeenand pushed to the new guild. The next agreeing pass,OnRosterCompletedor 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). MigrateAllremaps the per-guildseenInsets too, alongsideguildSeen, 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 reachseenInat all was not established; the remap closes it either way. One case inTests/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.
seenInstarted 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), andmembership_spec's fixtures gained the loaded stamp an in-guild client has. Mutation-checked: the reconcile on the raw union,StatusOfon 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_declareandsr_requestare 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 (whichSoftResSync.Distributethen 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 usedIsTrustedSenderalone, 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, andReport(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.Submitrefuses 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_setupis 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 whosefromfails the owner gate's live rule, never one raised by any character on this account. It runs fromRaidController.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, andrequestsguild_specfor 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 realComms: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 carryfrom, 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,LiveTrustwithout 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.PruneForeignshares a short name withRaidController.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_lockwas 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, soSettings.ApplyPushreplaced 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.EconAdmitson the cursor'sby;Declaration.LockAdmitson a LOCK's subject under the owner rule and on an UNLOCK's or LINK'sbyunder the author rule;Settings.PushAdmitson the stamp'sby(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_lockrefusals 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.foreignandSettings._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 afterPushAdmits.Tests/settingsguild_spec.luanow pins it on the realRaidStatus: 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.PruneForeignEconresets a foreign cursor to the unstamped default andDeclaration.PruneForeignLocksdrops foreign lock records (and refuses to run without both rules, so a missing module cannot empty the log). Both run fromRaidController.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 asrec.by).opts.raidersalso 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
guildSeenunion, 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. NewseenInset inCharacters' membership table,{ [guild stamp] = { [name] = true } }, written only byReconcileRosterandOnMemberJoinedunderRaid.PlayerGuild(), never from a peer;Characters.WasGuildmateOf(name, guild)reads it andAuthorTrustnow calls it.guildSeenand the standings are unchanged.Characters.WasGuildmatehas no production caller again and is back innocaller_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.ApplyPushnow holds refused pushes inSettings._pendingPush, andSettings.RejudgePendingretries them fromRaidController.WirePrune'sOnRosterReadyandOnRosterCompletedcallbacks. 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 mostSettings.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.PruneForeignguarded onlyIsReadyandIsInGuild, and LibGuildRoster goes ready on a SHORT login build, where a current officer missing from it reads as foreign. It now returns early unlessIsRosterComplete, the idiom DKP already used, andWirePruneregistersOnRosterCompletedso 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).
seenInis 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. NewDeclaration.GrandfatherLocks, called once fromDibs.lua's ADDON_LOADED before any sync can deliver a record (flagdeclLockGate), records every lock-log canon then held indeclLockHeld, andPruneForeignLocksskips those. Everything that arrives later is judged on arrival and by the prune. Not seeded fromguildSeen, 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). NewDKP.GrandfatherEcon, called next to it (flagdkpEconGate), records the held cursor'sbyandupdatedAtindkpEconHeld, andPruneForeignEconleaves 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 ofLedgerSync.PruneStamped,LootLog.PruneStampedorRequests.PruneForeignexists in v0.8.0, but the first two re-judge only entries flaggedprovisional, a flag this build adds, and none of the three reads the per-guild "ever" set, so none has this upgrade problem.Requests.PruneForeigndoes 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.Allempties 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.Allnow runs both grandfather functions on the emptied store, which records nothing and sets the flags.Wipe.IsEmptyand 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.luaruns both grandfather functions on a store holding a lock record and a stamped cursor and asserts every key they add is inWipe.GATE_KEYS, which pinsdkpEconHeld, 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).
ReconcileRosterread the library's roster and a freshGetGuildInfoseparately, 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 firesOnMemberJoinedandOnMemberLeftfor a SISTER guild's roster too (SetSisterRoster, keyed by that guild), andRegisterMembershipdiscarded the key, so a sister guild's joiner went intoguildSeen, 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).
ReconcileRosternow also runs onOnRosterCompleted, andWirePrunereconciles ahead of its own prune on both edges, because CallbackHandler dispatches withnextand Characters' handler could otherwise run after the prune. - The prune also waits for the guild name (finding 3):
PruneForeignreturns 0 whileRaid.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_CAPauthors 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_specandsettingsguild_spec;decllocksync_specgains 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, andepgprosters_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,AuthorTrustwithout its "ever" half,PruneForeignwithout the complete-roster guard, theOnRosterCompletedcallback doing nothing, andPushAdmitsadmitting 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 stubWasGuildmateOfin place ofWasGuildmate. Mutation-checked:AuthorTrustback onWasGuildmate,ReconcileRosternot 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 theloot_rollgate 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 toGrandfatherEcon),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'sdefaultalias, 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 throughPlanSpecFor.ApplyPlanContextnow 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: whenPlanSpecForreturns 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 resolutionWeights:Sourcenow uses. A custom template is registered for the alt's class and scored on thedibssource (exposed asWeights.SOURCEinstead of spelling the string a second time). Two newplanspec_speccases cover it; disabling the lookup turns both red. Tests/plannerdraw_spec.luagains a case that draws the real Planner, plans an alt with no spec picked and records whatApplyPlanContextpasses toSetScoringContext(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 theDibs-v0.5.1tag:setPlanCharcleared_planSpec(GUI/Planner.lua:964) andApplyPlanContextpassed it straight toSetScoringContext(: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:Drawto file level asPlannerUI.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).Drawkeeps a local alias. Mutation-checked: skipping the badge in that builder turns the new case red. - The custom-template case in
Tests/planspec_spec.luanow writes the template straight into the class store instead of going throughWeights:SaveCustom, which registers it itself and hid whetherWeights:ScaleFordid (N1). Deleting the register insideScaleFornow 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.luaSPEC_MAP[7]gainstank, so it reachesItems.CLASS_SPECS.SHAMAN(declarations, soft-reserve restrictions). Detection walks talent tabs 1..3 only, so nobody is detected as a tank; the player picks it.- The weights come from ItemDB (v1.5.0, inbox thread 46c74258): its stock "hawsjon" scale now ships
[7].tankon Vanilla, TBC and Forever, a labelled copy of paladin protection, since no published Classic shaman tank scale exists. ItemDB also confirmed Classic Era shamans can parry (Enhancement talent "Parry", spell 16268), so the scale's parry weight stands. Shamans get it as an ordinary "Tank (default)" weight template, andItems:ScoreForClassSpecscores it like any other stock spec. - Dibs' interim stand-in is gone: the block at the top of
Modules/Weights.luathat registered a private "tank" scale,DefaultTemplates'add("tank"),ScoreForClassSpec's fallback to that source, thetemplateLabel"(tank)" branch inGUI/Weights.lua, and theweights_tank_templatelocale key. A savedsource = "tank"(written by a dev build of this version) is folded to the stock scale by one local,live, on both branches ofWeights:Activeand inSaveCustom, because the library answers nil for an unregistered source and the template would otherwise open empty -- and Save As would copy the dead literal into every new template (Peer Review, thread af33e116 D2). - An ItemDB too old for the spec is now said out loud (Peer Review, thread af33e116 D1, correcting my own
"Dibs cannot detect it"): ItemDB made no MINOR bump, but
GetBiSWeightsanswers nil for a spec the scale lacks, so the data is the detect. NewWeights:MissingScaleSpecnames the active default template's spec when the installed scale has no row for it, and the Weights tab shows "Your ItemDB has no Tank weights ... Update ItemDB to v1.5.0 or newer" (weights_scale_missing) beside its scale-provenance note. It covers the player's own active template; another raider's declared shaman tank still scores nothing on an old ItemDB, with no note. SPEC_SCHOOLS' comment no longer calls the shaman tank's scale a stand-in (D3).Modules/SoftRes.lua: shamantankis a physical-camp spec and is in the built-in Tank restriction template.- Corrected two comments that described a shipped "tank" scale of protection/feral_tank specs; those live in
"hawsjon".
docs/Dibs-Integration.mdno longer lists awardenshaman token that is not in the data. Modules/SoftRes.luaCanReserve:tierannotatedstring?, clearing an editor warning.
New Tests/shamantank_spec.lua (17 tests, against the real LibItemDB): the Vanilla, TBC and Forever scales each
carry [7].tank, Dibs registers no "tank" scale of its own, the template seeds from the library's numbers, the
retired source is folded on the default branch, the custom branch and Save As, the old-ItemDB note fires with the
row removed and stays silent with it present, for a custom template, and with no spec, and it scores.
Mutation-checked: disabling the default-branch fold, the SaveCustom fold and the missing-row return each
turned its own test red. Not tried in
a game client. The numbers are still paladin protection's, not a measured shaman scale.
Tests -- the /dibs handler at full coverage, and three gaps in the specs around it
Peer Review, thread 7f41afea (reply 549a6628). Dibs.lua is at 182/182 lines.
Tests/locale_spec.lua's scan also matchesns.GetLocale()["key"]andC.GetLocale()['key'], not onlyL["key"], so a typo in a direct lookup (the/dibschat prints among them) no longer prints the raw key name with the spec green. The wider scan found no missing enUS key in the current tree. Red-first confirmed: with the two new patterns removed, only its self-test failed.- Every method name the
/dibsrouting stubs may fake (ROUTEDinTests/bootstrap_spec.lua) is checked to be a real function on the module that ships it, so a renamed method cannot hide behind a stub. - The second
Dibs.luaload in the no-C_AddOnstest unregisters theADDON_LOADEDframe it builds, so a later spec cannot run a second bootstrap over the saved variables.
New -- import Core Loot Manager's EPGP settings
Block 1.7 of docs/26 (Vishiswaz in #dibs, 2026-10-14: CLM "migrates all of your CLM data AND settings", naming
"the GP values of items"). /dibs import clmsettings [roster] previews a CLM roster's settings as Dibs' EPGP
settings and writes them only on Accept. Officers only. Settings are guild-wide, so a second roster overwrites the
first.
- Live CLM (
Import.ReadCLMSettings, through the_G.CLMglobal CLM publishes): GP scale (multiplier x base tier multiplier / 4.83, EPGPWEB equation at exponent base 2 only), per-slot multipliers as slot overrides, base GP fromminGP, each EP award (0 when CLM has it switched off), the interval in minutes, standby EP asfloor(boss EP x benchMultiplier), per-boss EP folded across difficulties, and per-item GP. - Per-item GP becomes a substitute item level (
EPGP.SetTokenPrice), so the price stays inside the one GP formula and moves with the scale.Import.SolveItemLevelsolves atgp + 0.5and checks the answer by pricing it back; it keeps the item level unrounded when two decimals would change the GP. A non-gear item is priced as a chest piece.Import.ApplySettingsprices every written item again afterwards and reports any that no longer match. - Pasted JSON export (
Import.CLMSettingsFromExport): CLM's export carries only the flatconfig, so it brings base GP and the EP awards. The preview says what it could not read. - Reported, not imported: the Wowpedia equation or another exponent base, a zero slot, a boss priced differently on two difficulties, an item priced per bid tier, starting EP/GP, hard and weekly caps, class multipliers, hard-mode boss values.
- New
Import.SETTING_ORDER,ImportUI.SETTING_LABEL_KEY, and locale keysimport_settings_*,import_set_*,import_why_*andimport_usage_settings. The roster pickerchooseRosteris now shared by the standings import and the settings import.
Peer Review's findings on the first version (thread c3b921b6), fixed here:
- A DKP roster could be imported as EPGP settings. The pasted export was never checked, and the live path
checked only when CLM's constants carried a name I had guessed. CLM's constants are now read from its source
(
POINT_TYPE = { DKP = 0, EPGP = 1 },SLOT_VALUE_TIER.BASE = "b",ITEM_VALUE_EQUATION.EPGPWEB = 1) and kept as fallbacks (Import.CLM_*). The export'stypefield is compared with them. A roster whose type cannot be read still previews, with a note asking the officer to check. minGP = 0became a base GP of 0, which leaves every raider with no GP without a priority (EPGP.PRanswers nil). AminGPbelow 1 is now a note, not a write.Settings.MIN.epgpBaseGP = 1refuses it from a hand edit or a push too. NewSettings.MigrateBelowFloor, run fromDibs.luaat ADDON_LOADED besideMigrateEarnMode, clears a value saved below its floor before that floor existed, so the default (1) applies, and prints one chat line naming the change (settings_below_floor). It does not move the settings stamp. KNOWN COST: an officer on an older Dibs who still pushes a base of 0 has that one key refused by newer clients (ApplySystemwrites throughSettings.Set), so the guild disagrees on base GP until that officer updates; the load-time repair on their own client, once updated, ends it. It also applies to a GP scale saved below its floor, which goes back to its default. Three tests inTests/bootstrap_spec.lua(one holds the chat line's labels toSettings.MIN) and one inTests/clmsettings_spec.lua, mutation-checked.- A failed read of a loaded CLM said CLM was not loaded and opened the paste box. It now answers
read_failedwith the error text. - The setting keys were listed twice with nothing checking they agreed. A spec now holds the GUI's label table to the module's list.
- Removed an unreachable
return ""fromsettingsSubjectinGUI/Import.lua.
New Tests/clmsettings_spec.lua (53 tests). Sixteen production lines were broken one at a time across two
rounds, each turned its test red, and each was restored. One test sends an unrounded token item level through the
real AceSerializer and EPGP.AdoptTokenPrices and checks the receiving client charges the same GP.
Modules/Import.lua and GUI/Import.lua are at 100% line coverage. Not verified: CLM's data shapes come
from reading its source, not from running it (CLM is not installed here). That CLM's boss ids are the game's
encounter ids is assumed. Standby EP is approximate once a guild has per-boss values. An absent
autoAwardIncludeBench is assumed to mean the bench is paid. Nothing was tried in a game client.
New -- loot council voting, per item
Block 2.1 of docs/26, designed in docs/27, under directive 09b5a6f0: loot council is a per-item decision, not a loot method, and a council item costs no points. The master looter sends one row of the loot table to the council ("Send to loot council", first in the row menu, pending rows only, never on a live table). Raiders answer it Main spec, Off spec or Bid like any row and can change their answer while it is open; council members vote on the board; the master looter awards from it.
- Model (
Modules/LootCouncil.lua, new):ParseList,QualifyList,IsMember,Context,AmMember,Candidates,Vote,Prune,VotersFor,Tally,VoteOf,VotePayload. The master looter is always a member; guild officers are members whilelootCouncilOfficersis on; the rest come from the typedlootCouncillist. - Session (
Modules/LootSession.lua): acouncilmode that takes no/rolls and treats a raider's answer as interest. In an EPGP raid a council row is the council's, not EPGP's, in bothAddChoiceandResolve. - Loot table and master looter (
GUI/LootTable.lua,GUI/Masterlooter.lua): newML:CouncilTableRow, cloned fromAwardTableRow;UI:CouncilRow,UI:VoteCouncil,UI:OnCouncilVote; a Vote button and a votes column on the board (shown to council members only, the leader in gold for the master looter, the voters in the button's tooltip, a second click withdraws). A council award skips the SR+ reset, the static price and the EPGP charge, and its winner line reads "by the loot council". A soft-reserve loot-council hold row now opens for answers instead of staying closed. - Sync (
Modules/LootTableSync.lua): the board payload carriescouncilandvotes; a newloot_council_votemessage that only the raid's master looter acts on. - Settings:
lootCouncil(default empty) andlootCouncilOfficers(default on), officer-owned and synced, on the Rolls tab beneath Distribute Later.B:Texttakes an optional normalizer so the list saves throughQualifyList. 24 new locale strings. - Peer Review fixes (threads 565dc454 and 28ba50a0):
VotersFormatched names by exact spelling and came back empty for a differently written name; a typed name without a realm now gets the officer's realm on save, so a same-named stranger from a connected realm cannot vote; a raider's answer on a council row must be Main spec, Off spec or Bid, so a modified client cannot put free text on every member's board; and the master looter's client runs a vote's sender throughNames.Fullbefore the membership check, so "Ana" from another realm is not counted as the council's "Ana-RealmB". - Removed:
councilas a loot-method input mode inModules/LootMethods.lua, with its spec and the docs/10 lines that offered it, per the same directive.
New Tests/lootcouncil_spec.lua and Tests/mlcouncil_spec.lua (18 tests); every production line they name was
broken, went red and was restored. Tests/srhold_spec.lua and Tests/lootmethods_spec.lua pinned the old closed
council row and now pin the new shape; Tests/settingsui_spec.lua counts the new toggle and text box. Not tried in
a game client: board layout, button overlap and column widths are unverified.
New -- EPGP rosters for raid groups running at once, and alts that share their main's points
Asked on Discord, as Core Loot Manager has both. Design in docs/28-epgp-rosters.md.
- A roster is its own ledger pair. Main is the existing
epgp_ep/epgp_gp, so no data migrates and a guild with no rosters sees no change. Another roster isepgp_ep@<id>/epgp_gp@<id>with its own sync types. Not a roster field on each entry: an older client drops unknown fields and relays the entry without it, so two current clients could file one entry under different rosters. The id comes from the name (EPGP.RosterId), so two officers creating "Tuesday" get one roster. The list (DibsDB.epgpRosters) rides the settings push asepgpRosters; removing a roster deletes no points. - A raid's roster is
pointsRosteron its record (Modules/Raid.lua), absent meaning Main, set only by the raid's starter (RaidController:Start(raidKey, roster),MoveToRoster); a move back to Main is written as an explicit"main"(that alone did not stop an older relay undoing a move -- see thepointsRosterAtfix below). Standings, awards, charges and adjustments default to the roster of the raid this client belongs to, else Main, so the loot window charges the right pool with no change of its own. Undo and re-award find the charge in whichever roster holds it. Decay with no roster decays every roster. Economy settings are shared by all rosters. poolAltPoints(officer-owned, off by default): every shown or spendable EPGP and DKP number is the account's total across linked alts (DKP.PooledBalanceOf,DisplayBalances, pooledEPGP.Standings). Entries still name the character who earned or won, so turning it off restores each balance exactly. (This line first said decay, import and the zero-sum split read per-character balances and that decay costs a pooled account at most a point per character. That was wrong for DKP and too loose for trust; see the self-audit fix below.)- DKP gets pooling, not rosters: its bank, round-robin cursor, seasons and auctions would each need the roster
threaded through, and the request was for EPGP. Filed in
docs/08-open-questions.mdwith three more: per-roster economy settings, moving a raid's existing entries when its roster changes, and decay's default once a picker exists.
New Tests/epgprosters_spec.lua (38 tests), ten mutations each caught. Modules/EPGP.lua, Modules/DKP.lua and
Modules/RaidController.lua stay at 100% line coverage.
Where it shows (GUI/Settings.lua EPGPSettingsUI.DrawRosters, GUI/Tracker.lua TrackerUI.RosterPicker): the
EPGP tab has a roster picker plus New / Rename / Remove (guild rank only), and the picked roster drives that tab's
standings, Adjust, Undo and Run decay, so Run decay now decays only the picked roster. The pooling checkbox sits
under the EPGP master toggle and stays live with EPGP off, since it governs DKP too. The Live raid pane has a roster
picker under Start/End, hidden while Main is the only roster; picking on an open raid you started moves it. The
Tracker's DKP column reads DKP.DisplayBalances, so it pools like EPGP's. /dibs import has no window, so it imports
into the roster picked on the EPGP tab, and its preview names that roster once there is more than Main. Specs:
Tests/epgpsettingsui_spec.lua (11), Tests/trackerraid_spec.lua (10), Tests/tracker_spec.lua (1),
Tests/import_spec.lua (4), each mutation-checked; Tests/epgpundo_spec.lua's non-officer test was typing into the
new roster box and passing for the wrong reason, and now indexes the right box. Not tried in a game client, and
live roster sync between real clients is unverified.
New -- officer-set answer buttons and a raider note on loot council rows
Block 2.2 of docs/26. An officer types up to four answers (twelve characters each) into lootCouncilResponses,
e.g. "BiS, Upgrade, Minor"; rows the soft-reserve setup flags "Loot council" then offer those plus Pass. Empty, they
keep Main spec / Off spec / Pass.
- The wire carries a token (
r1..r4), never the label; the labels travel with the row and every reader sanitises them.LootSessionAddChoice's council branch takes Main / Off spec / Bid plusr1..rNfor the N answers that row offered, throughLootCouncil.IsChoice, and nothing else. - The note is capped at 80 characters and stripped of colour, texture, atlas, link and newline escapes, every
|and any split UTF-8 character -- on the raider's client, again when the master looter stores it, and again when a board draws it; dropped if the sanitiser is not loaded. Council members see it in a Note column and tooltip; raiders do not. - Model (
Modules/LootCouncil.lua):Sanitize,SanitizeNote,ParseResponses,Responses,RowChoices,ResponseIndex,IsChoice,ChoiceLabel;Candidatesranks configured answers first and carries the note.Modules/LootTable.luarows carryresponsesandSelect/Adopttake a note;Modules/LootTableSync.luasends the note sanitised and the board carries it on council boards only.GUI/LootTable.luadraws the buttons, a Note button and its popup (UI:SetNote,UI:PromptNote). - Setting
lootCouncilResponses, officer-owned and pushed withlootCouncil, on the Rolls tab. - The council vote now fails closed (Peer Review thread 28fba7ce):
UI:OnCouncilVoterefuses a vote whenNames.Fullis missing or returns nothing, instead of falling back to the bare sender and reopening the cross-realm hole fixed above.
New tests in Tests/lootcouncil_spec.lua (15), Tests/mlcouncil_spec.lua (8) and Tests/srhold_spec.lua (2),
each production line mutated red and restored; Tests/settingsui_spec.lua counts the new text box. Not every
Sanitize strip was mutated, only the texture one. Not tried in a game client.
New -- why an item went to the disenchanter or the bank is in the loot log
Block 2.3 of docs/26. Disenchant and Bank are a new loot-log kind, a disposal (LootLog.RecordDisposal): the
recipient goes in its own to field, never in winner, so nobody's soft-reserve plus is reset and nobody's points
move. winner holds a placeholder no character can be named ("@disenchant", "@bank"), because every client's
Accept, older ones included, refuses a row with no winner and sync would re-offer it forever. The SR flag is
forced false in RecordDisposal, Record and Accept, and every reader skips the kind by name: WonSRItemIn,
SRWinnersIn, SRWinnersInRaid, WonBefore, EPGP's charge matching, Forfeit/Reattribute and the Tracker CSV.
On an older client a disposal lands as a non-SR row won by "@disenchant", which nothing there can credit or charge
to a real character.
- Recorded from
disposeToEnchanter(GUI/Masterlooter.luarecordDisposal), so every disenchant path writes one: the row button, the DKP no-bid outcome, the disposal roll and an owed item. Also from the no-bid Bank branch. - A loot council award carries
reason = "free": a real win that charged nothing. - The Loot Log tab reads "Disenchanted (Ana)", "Guild bank (Mel)" or "Bob (free)", Type "Not won".
New tests in Tests/lootlog_spec.lua (9), Tests/disenchant_spec.lua (2 plus a bank assertion) and
Tests/mlcouncil_spec.lua (1), all mutation-checked. Not built: officer-defined reasons, a Free hand-out outside the
loot council, and a reason on a held "Guild bank" row handed out by hand.
Fixed -- disenchanting an owed item still counted it as the absent winner's
ML:RerollOwed forfeits the first winner's loot-log row once the new roll finds a winner; ML:DisenchantOwed did
not. So when a Distribute Later winner who had left had their item sent to shards, they still read as having "won
it before". DisenchantOwed now forfeits the same row after the disenchant goes through, and not when it is
refused. Pinned in Tests/distributelaterml_spec.lua; removing the forfeit turns it red.
Corrected (self-audit 1bccec03, finding 6): this entry and the comment in ML:DisenchantOwed first said the
forfeit also fixed an SR winner's plus "staying reset". It does not. The SR+ readers (WonSRItemIn, SRWinnersIn)
ignore forfeits by design (Modules/LootLog.lua Forfeit's note: re-crediting a forfeiting player is a decision
nobody has made), so an SR winner's plus stays reset either way. Only the "won it before" half is fixed.
Fixed -- shared alt points trusted any self-declaration, and paid or charged per character
Self-audit 1bccec03, findings 2, 3, 4, 7, 8 and 10, with Peer Review's rulings on the thread.
- Pooling only trusts an officer's link (finding 4). New
Declaration.LinkSnapshot(the one link walk, shared withMainOf),ConfirmedMainandPoolMainOf. A bare self-declaration no longer pools: saving a declaration locks it, so a lock filtered nothing, and a pug who declared an officer its main could spend the officer's account and push its GP into the officer's PR. Nothing in the declaration model lets a main confirm an alt (a lock'sbyis self-asserted; same-account proof exists only on the owner's client), so an officer link record is the only trusted source. If the owner saves a declaration after the link, it must name the same main. The DKP pool (DKP.LivePool), the EPGP PR fold anddkpAltSpend(SpendableOf/ChargeFor, viaDKP.PoolResolver) all use it. Behaviour change: under alt-spend an alt reaches its main's points only once an officer links it. The account views still group by declarations, where a false claim only mislabels a row. - The earning dedupe uses the same rule, pooling on or off (Peer Review's re-review).
DKP.LiveOncePerAccount(shared by EPGP's boss award) andDKP.LiveStandbyresolved accounts through the bareDeclaration.MainOf, and the dedupe keeps one character per account, so a pug who declared an officer its main and was seen later took the officer's kill, and a benched officer earned no standby while that pug raided. Both now takePoolMainOf. Cost: an unlinked alt raiding while its main is benched earns both, as two unrelated characters would, until an officer links them. Two cases inTests/poolwriters_spec.lua, red against the old lines (run 20261014-092410.665-1048316769). TheDKP.SpendPoolcomment now namesPoolMainOf; docs/28 records the import and season-leaver decisions and that a link record is trusted, not unforgeable. - The declaration lock uses it too (Peer Review thread 60a3198d).
Declaration.AccountKeyOfandAccountCharsresolved the lock's account through the bareMainOf, so a pug who saved "my main is Bob" minted a LOCK on Bob's account (re-locking Bob after every officer unlock), an unlock approved for the pug released Bob, and pressing Unlock for Bob approved the pug's pending request. Both now takePoolMainOf. Cost: an unlinked own alt locks and unlocks as its own account; an old lock record filed under its main no longer reaches it, so it falls back to its own savedlockedflag and stays locked. Three cases inTests/officerlink_spec.luared against the old lines (run 20261014-093840.206-3822737171) plus one pinning that an officer-linked alt still shares the lock; threeTests/declunlock_spec.luacases that linked the alt by bare claim now link it by officer record. - Under pooling the account is the unit for every writer whose amount depends on a balance or is paid per
member (findings 2 and 3); the comment claiming the fold "never reaches what is written" is rewritten. DKP decay
charges each account
floor(total * pct / 100), spread over its positive characters by the deterministicDKP.Spread, and skips an account at or below zero (a +300 main with a -200 alt lost 30 of a 100 account). EPGP decay keeps exactlyfloor(|total| * keep)per account (EPGP.PooledDecayDeltas) and now writes in sorted order. The starting balance is paid once per account (NewcomerstakesmainOf); a season reset brings the main to N and linked characters to 0 (PooledResetAmounts); the absence penalty is once per account and none if the account attended on another character (AccountAbsentees).DecayPlan/ResetPlanfeed both the writers and the DKP tab's Season preview, so the count an officer confirms is the count written. Pooling off: unchanged. - Run decay names the roster it decays, in its tooltip and confirm (finding 7).
- Remove roster removes an empty roster (finding 8):
EPGP.KnownRosterscounts a ledger only when it holds an entry (ledger tables are created on first read); a roster a raid ran on stays. - The DKP tab pools (finding 10): per-character rows read
DKP.DisplayBalances, and the group-alts view groups by the pool's links, so a raider sees the same figure as the Standings tab and bid popup.
New Tests/poolwriters_spec.lua (36 cases, one per writer under pooling plus the trust, decay, reset, start and
roster cases): 14 red against the unfixed code, then two mutation rounds with every targeted case red. Alt-spend
fixtures in dkp_spec, dkpauction_spec, dkpbid_spec and epgprosters_spec moved to officer link records. Known
and left: a character imported under pooling keeps the imported figure each, and an archived alt cleared at season
end takes its own points out of the shared account.
Fixed -- giving a shard debt to the disenchanter cancelled an unrelated win
Self-audit 1bccec03, finding 5. The trade-timer menu offered "give to disenchanter" on an enchanter's own shard
debt (reason == "de"); ML:DisenchantOwed then forfeited LootLog.PriorWin(enchanter, item) -- that enchanter's
newest real win of the item, possibly from another week -- and logged a second Disenchant row. TradeTimer.ChoiceRows
no longer offers it and ML:DisenchantOwed refuses a shard debt before disposing anything. The re-roll path in
ResolveRoll (activeReroll) had the same forfeit and now skips it for a shard debt. Three new specs in
Tests/distributelaterml_spec.lua and Tests/tradetimer_spec.lua, each red against the unfixed code.
Fixed -- a raid's EPGP roster could stick on the wrong roster behind an older relay
Self-audit 1bccec03, finding 1. A build without pointsRoster relayed the author's move to Main as a newer envelope
with no roster; a client holding "tuesday" kept it, and the author's real copy at the same updatedAt could only
fill an absent field. Raid.EditDigest left out Main, so the copies never converged, and a master looter on that
client paid raid start/end EP into the wrong roster. The record now carries pointsRosterAt, the roster's own
clock, set by Raid.New and Raid.SetPointsRoster; Raid.Accept merges the roster later-stamp-wins in both
branches (mergeRoster) whatever updatedAt says, an absent field loses to any value, and two values with one
stamp break the tie on the larger id. The digest folds in the raw value and its stamp, explicit "main" included;
Raid.Wire carries the stamp. Four specs in Tests/raid_spec.lua ("a roster move survives an older relay"); three
were red against the old code, the fourth went red when the stamp comparison was flipped.
Fixed -- the main/alt link import could fold an alt into a third person's account
Self-audit 1bccec03, finding 9. classifyLink (Modules/Import.lua) never checked whether the import's main was
itself an alt in Dibs: Guild Roster Manager saying A belongs to M while Dibs had M under X wrote A to M, putting A on
X's account, and only M's own row was flagged. It also overwrote a character that had declared itself its own main.
Both are now reported as a conflict (MAIN_ELSEWHERE, SELF_MAIN, with their own preview text) and never written;
the preview and the accept-time re-check use the same classifier. The Tests/importalts_spec.lua case that asserted
the alt "still links" pinned the bug and now asserts the conflict; three new cases, each red on the old code.
Tests -- raid controller line coverage to 100%
Four tests at the end of Tests/raidcontroller_spec.lua drive the last seven uncovered lines of
Modules/RaidController.lua: a party leader on their own roster, two open raids started in the same second, UnitKey
with no name normaliser, and PLAYER_LOGIN registering the raid sync and marking a stored raid handled. Each
mutated red and restored; no code changed. Modules/EPGP.lua, Modules/DKP.lua and Modules/RaidController.lua
are at 100% line coverage.
New -- a re-awarded EPGP charge moves its Loot Log row
The re-award below used to move the GP and leave the Loot Log naming the first winner. Now an item charge is
stamped with the Loot Log row it pays for (loot, outside the canon, carried by sync; LootLog.JustRecorded
finds the row the award site wrote just before the charge), and EPGP.ReawardCharge re-attributes that row
through LootLog.Reattribute (the Distribute Later forfeit plus a new row for the new winner), then stamps the new
charge with the new row so a second re-award follows it. An older charge with no stamp falls back to
LootLog.FindAward (same winner and raid, closest in time), which refuses to choose between two equally close
rows; with no row the GP moves alone and nothing is written to the log. New LootLog.RowByCanon.
Fixed -- two master looters undoing one charge refunded it twice
EPGP.UndoCharge and DKP.UndoAward refuse a second undo only against entries this client already holds. Two
clients that each believed they held the master looter role could reverse the same charge before either synced;
both reversals survived the merge and the winner was refunded twice. A concurrent re-award also charged two raiders
for one item. (Peer Review G2, thread 5771ab04.)
The fix is a read-time rule, because the authoring guard cannot see an entry that has not arrived:
Ledger.FirstUndoWins keeps the earliest reversal of each target (then by author, then key), the same pick on every
client in any arrival order. The ledger takes a discount function (Ledger.New), and BalanceOf, AllBalances,
the new Effective and History all leave the losers out; nothing is deleted. Bound:UncountedCanons memoises
per store and entry count. EPGP.Discounted also drops a losing re-award's new charge (the charge written straight
after its reversal, same author and moment) and any reversal of a charge that does not count. DKP.Discounted
groups a reversal by author and moment, since one UndoAward writes one entry per raider.
Also fixed while writing this entry: EPGP's reversal test now requires a loot entry (KIND_RAID), as DKP's always
did. An officer's Adjust takes a free-text reason, and one reading undo:<canon> used to mark that charge undone,
so the master looter's real Undo was refused and the GP stayed charged.
New Tests/concurrentundo_spec.lua (19 tests), written red first and mutation-checked; the officer-note test went
red with the kind check removed (run 20261014-045357.246-3538300151).
New -- import main/alt links from Guild Roster Manager or Ultimate Guild Roster
Asked by Vishiswaz on Discord (thread c43e1dd4). /dibs import grm and /dibs import ugr read the alt groups of
whichever of the two is installed and link each alt to its main as an officer's link
(Declaration.SetLinkAsOfficer), the same record the Accounts tab's "Points go to" correction writes. Neither addon
is a dependency. GRM's saved GRM_Alts table is read directly, because its public GetMemberAlts returns alts
without the main; UGR's UGR.Alts:Listed() view is read rather than its saved variables, because the view resolves
overlapping groups.
Conflicts are reported, never overwritten: an alt is linked only when Dibs has it on no account. A row is shown and
left alone when Dibs already has the alt on a different account, when other characters are linked to it, or when
the source's group has no main. New in Modules/Import.lua: AltGroupsFromGRM, ReadGRMAlts, ReadUGRAlts,
FromAltGroups, PreviewLinks and the apply; GUI/Import.lua previews the plan. New Tests/importalts_spec.lua.
Fixed -- a test that only failed when another ran first
Tests/epgpsettingsui_spec.lua found the officer-adjustment boxes by counting every box above them. After
Tests/epgp_spec.lua ran, the item library it had loaded was still in place, the EPGP tab drew one box per raid
boss, and the count picked a boss box for the character. Now epgp_spec unloads what it loaded in a teardown,
and the boxes are found by their tooltip titles instead of their position (Peer Review R1 and R2, thread e8f2b057).
The first adjustment test also prints the panel's refusal line when it fails.
New -- EPGP undo and re-award of an item charge
The master looter can now give back the Gear Points for a won item, or move them to whoever really got it (docs/26 block
1.6, DKP's UndoAward for EPGP; Core Loot Manager's EPGP plugin reverses GP on a re-award). An EPGP charge is one GP
entry whose reason is the bare item, so unlike a DKP award group it is keyed by its canon. New in
Modules/EPGP.lua: UndoReason(canon) (undo:<canon>), UndoneSet(entries), UndoableCharges(limit) (item
charges not yet undone, newest first), UndoCharge(canon, by, at) and ReawardCharge(canon, newSubject, by, at).
- Undo writes one loot entry (
KIND_RAID, like the charge itself) of minus the charge, under the charge's raid, and refuses a second undo (already_undone), an unknown canon (not_found) and anything that is not an item charge (not_charge). Nothing is deleted; the history shows the charge and its return. - Re-award is that undo plus an ordinary
itemcharge of the same GP to the new character, under the same raid, so the new charge can itself be undone or moved. Same GP, not a re-price: the officer's settings may have moved since the drop. Refuses a blank name (no_char) and the current holder (same_char). - Only the master looter may undo or re-award (
not_ml), throughDKP.MayAuthor, the gateDKP.UndoAwarduses. I first shipped this ungated and writing an officer entry, arguing that whoever sees the tab can already Adjust. Peer Review's G1 (thread 5771ab04) showed that was wrong: the tab is drawn forCanManageLoot, which admits a loot leader with no guild rank, and every other client'sLedgerSync.Authorizerrefuses an officer entry whose author has none. So a non-officer ML's undo stayed on their own client while a re-award's new charge synced, charging two raiders for one item everywhere else. New specs run the entries through the real authorizer as a receiver with a non-officer ML as author. - The same hole in Adjust and Decay, closed in the tab. Both write officer entries behind the same
CanManageLoottab, so a loot leader without guild rank wrote corrections only their own client kept. Both buttons now refuse withoutComms:IsOfficer, saying why (epgp_adjust_err_not_officer).Tests/epgpsettingsui_spec.lua's officer-action block now states its officer stub instead of inheriting aCanManageLoot-only one from an earlier block. - An undo refunds what was charged, not what is left after a decay -- DKP's undo does the same. A decay between the charge and the undo leaves the member slightly below where they would have been; Adjust corrects it.
- Tab: under the EPGP tab's officer actions, after Adjust and Decay,
EPGPSettingsUI.DrawChargeUndo: an "Item charge" menu of the last 15 undoable charges (ChargeMenuItems, pure; each row names the raider, the GP and the time of the charge, because the charge carries no item and two wins at one price would otherwise read the same), a "Re-award to" box resolved through the Adjust rows'ResolveSubject, and Undo charge / Re-award buttons, each behind a confirm that names the charge. The pick is cleared after a success.Heightcounts the four new rows. - Not built at first: re-attributing the Loot Log row on a re-award. Built since; see "a re-awarded EPGP charge moves its Loot Log row" above.
The tab refuses a non-master-looter's Undo or Re-award BEFORE its confirm (EPGP.MayUndo, exposed for that),
rather than after they have said yes; found in my self-audit of the G1 fix, each half mutation-checked (runs
20261014-041430.703-2741560458 and 20261014-041438.078-1098259568).
New Tests/epgpundo_spec.lua (20 tests); Tests/epgptokens_spec.lua's box count adds EPGPSettingsUI.UNDO_BOXES.
Mutation run 20261014-035617.429-4095109104: the second-undo guard, the re-award's new charge, clearing the pick and
Undo's nothing-picked refusal each turned their tests red (6 failed). The G1 fix's mutation runs: the reversal back
to KIND_OFFICER, no ML gate and no Decay officer gate turned 4 red (20261014-040906.413-2961309219); no Adjust
officer gate turned 1 red (20261014-040926.166-1467939027). Restored, whole suite 6278 passed, 0 failed (run
20261014-041642.684-3243597427). Not verified in a game client.
Also: ML.EPGPPrice's doc comment now says a tier token is unpriced only until an officer prices it (Peer Review nit
on thread fec7242e).
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

