promotional bannermobile promotional banner

VibeCheckLoot

Unlisted
VibeCheck is a full-featured EPGP loot management and attendance tracking addon built for World of Warcraft: The Burning Crusade Classic Anniversary

File Details

v1.2.6-bcc

  • R
  • May 9, 2026
  • 180.33 KB
  • 29
  • 2.5.5
  • Classic TBC

File Name

VCLoot-v1.2.6-bcc.zip

Supported Versions

  • 2.5.5

# VibeCheck Changelog

## 1.2.6 — May 8, 2026

### Bug Fixes — Permission Consistency for Non-Officer Raid Leaders

A non-officer Veteran Raider holding the raid leader slot would silently fail in three places:

**Attendance.lua — auto-start of raid tracking would silently bail**
- `OnPlayerEnterCombat` and `OnEncounterStart` correctly fire on the raid leader's client (`isRaidLeader()` check), but they then call `self:StartRaid()` — which had a stricter `isOfficer()` gate. Non-officer leaders saw a brief permission warning the moment combat opened and the raid never auto-started. Loosened to `canManageRaid()` (officer OR group leader) — same authority pattern as `canRunLoot` in `Loot.lua` and `EndRaid` already uses.

**Whisper.lua — `!standby` sent to a non-officer raid leader was redirected**
- Last version's anti-desync gate refused to process the bench-add unless the recipient was an officer. A non-officer raid leader received `!standby` whispers, replied "please whisper an officer or raid leader", and the whisperer was sent to find someone else. Now accepts the raid leader too via the new shared `ns.canManageRaid` helper.

**Attendance.lua — streak bonus would land only on a non-officer leader's own client**
- `CheckStreakBonus` (called from `EndRaid`) broadcasts per-player DELTAs, which receivers gate on `senderIsOfficer`. If a non-officer raid leader ended the raid, the streak bonus would apply locally on their client and **nowhere else** — visible to the leader and invisible to every raider. Now skipped entirely on non-officers; either an officer ends the raid (streak fires for everyone) or no one's streak is awarded (consistent state across the guild).

### Version

- Bumped from **1.2.5** to **1.2.6** — non-officer raid leader permission fixes.

---

## 1.2.5 — May 8, 2026

### Bug Fix — Tier Tokens Charged Far Less GP Than the Gear They Redeem For

Tier-token GP was being computed against `GetItemInfo`'s metadata for the *token itself*, not the redeemed gear:

- **`equipLoc` is empty** on tokens (a token isn't equippable), so `slotFactors[""]` falls through to the 1.0 default — wrong for any gear slot whose factor isn't 1.0 (shoulders, hands, wrists, waist, feet, neck, finger).
- **`ilvl` is the token's** item level, which for **T6** (Hyjal / Black Temple) is 141 while the redeemed gear is 146, and for **Sunwell tier** the token sits a few levels below its redeemed counterpart too. Because the GP formula has an exponential ilvl term, a 5-level shortfall translates into roughly a **30 GP undercharge** on every T6 piece awarded as a token.

A `Loot:ComputeGP` lookup now consults the new `ns.C.TIER_TOKEN_INFO` map (in `Constants.lua`) before the formula runs, substituting the canonical `(slot, ilvl)` of the redeemed gear for every TBC tier token (T4 / T5 / T6 / Sunwell + the Sunwell Sin'dorei Pendant and Band tokens). After this fix, awarding a tier token charges the same GP as awarding the redeemed gear directly.

Per-item `CustomItemGP` overrides still take priority — officers can manually override any token's GP if they prefer different values.

### Version

- Bumped from **1.2.4** to **1.2.5** — tier-token GP correctness.

---

## 1.2.4 — May 8, 2026

### Bug Fixes — EP / GP Correctness

**Attendance.lua / Sync.lua — bench/attendees double-credit on boss kill**
- A raider who whispered `!standby` while still in the raid group ended up in **both** `raid.attendees` (auto-snapshotted by `SnapshotRaid`) and `raid.bench` (added by `BenchAdd`). On every boss kill they were credited as an attendee AND as bench — double EP. Fixed three ways:
  - `BenchAdd` now removes the player from `raid.attendees` (mutual exclusion at the source).
  - `SnapshotRaid` skips bench-listed players when auto-adding raid members to attendees (so a re-snapshot can't undo the BenchAdd removal).
  - `OnBossKilled`, `OnBossWipe`, `MassAwardEP`, and the `BATCH_EP` receiver all skip attendees who are also on bench — defense in depth in case the two sets ever drift on a single client.

**Sync.lua — bench updates didn't propagate, BATCH_EP credit diverged across clients**
- `BenchAdd` and `BenchRemove` only mutated the local `raid.bench` table; there was no incremental sync. Officers' clients had the correct bench, but every other receiver's bench stayed empty until the next FULL sync. The result: the leader credited bench EP on their own client; every other client either credited the same player as a regular attendee, or didn't credit them at all. Fixed by adding two new RAID-message subtypes:
  - `BENCH_ADD` — broadcast on every `BenchAdd`; receivers add the name to their `raid.bench` and remove it from `raid.attendees`.
  - `BENCH_REMOVE` — broadcast on every `BenchRemove`; receivers remove from `raid.bench`.
- Authority on receive: leader-or-officer (matches the rest of the RAID-subtype branch).

**Whisper.lua — `!standby` from the wrong recipient silently desynced bench**
- The `!standby` handler ran on whichever raider received the whisper. If that raider wasn't an officer or the raid leader, the resulting `BENCH_ADD` broadcast would be dropped by every other client's authority gate, leaving bench out of sync. Handler now only processes on officers/raid leader; non-authoritative recipients reply telling the whisperer to message someone who can act.

### Wastefulness — `StartRaid` migrated from N DELTAs to BATCH_EP

- `StartRaid` previously sent up to **50 individual `BroadcastDelta` messages** for a 25-player raid (one per attendee for `epPerRaidStart`, another per attendee for `epPerTimeOnTime`). Each was small and queued at 50 ms, so the per-second rate stayed safe — but it was wasteful traffic that queued behind any concurrent FULL transfer. Migrated to `BATCH_EP`: at most **2 messages total** per raid start (one per EP type), regardless of raid size.

### Memory — `MAX_RAIDS` cap on local raid storage

- The `raids` table was unbounded; a year-round raiding guild would slowly accumulate megabytes of stale raid records (kills + attendees + bench × hundreds of weeks). New `MAX_RAIDS = 500` cap trims the oldest *completed* raids when exceeded — about a full year of weekly 25-mans, well past any window `AttendancePct` looks at. In-progress raids are preserved regardless of age.

### Disconnect-Risk Audit — clean

A full pass over every outbound code path in the addon:
- All chunked sends still route through the 50 ms / 20 chunks/sec ticker.
- No new code path introduces a tight loop that bypasses the queue.
- `BENCH_ADD` / `BENCH_REMOVE` are single small messages.
- `StartRaid`'s migration to `BATCH_EP` *reduces* peak outbound (2 messages instead of 50).
- The new `MAX_RAIDS` trim is local-only — no network traffic.

No DC vector identified. Same safety profile as 1.2.3.

### Version

- Bumped from **1.2.3** to **1.2.4** — bench-credit correctness + StartRaid efficiency.

---

## 1.2.3 — May 8, 2026

### Bug Fixes — Boss Kill EP

**Attendance.lua / Constants.lua — Eredar Twins triple-credited**
- Eredar Twins (Sunwell Plateau) awarded boss-kill EP **three times per encounter**: once when Sacrolash died (`COMBAT_LOG`), once when Alythess died (`COMBAT_LOG`), and once when `ENCOUNTER_END` fired for the encounter as a whole. The cross-handler dedup keyed on `bossName:lower()` and the three calls had three different names, so none collided. Fixed by introducing an `encKey` parameter on `OnBossKilled` and a new `ns.C.NPC_TO_ENCOUNTER` map: every peer NPC of a multi-NPC encounter and the canonical encounter name all hash to the same dedup bucket. The dedup window also widens from 5 s to 60 s so a peer NPC dying a few seconds after its sibling doesn't slip past.
- Existing single-NPC bosses are unaffected — `encKey` defaults to `bossName` and the dedup behaves identically.

**Attendance.lua / DB.lua — silent history-row duplication on FULL sync after a boss kill**
- `OnBossKilled` (and `OnBossWipe`, and `MassAwardEP`) snapshot a single timestamp `ts` and pass it as a new optional `atTime` arg to `AwardEP` so every locally-inserted history row uses the same time. Previously `AwardEP`'s internal `time()` could drift across a second boundary on a slow 25-player loop, producing rows whose `histKey` (`time | type | who | amount | by`) didn't match the receiver-side rows the BATCH_EP broadcast created. On the next FULL sync, `mergeHistory` saw the sender's `T+1` rows as new entries and added them on top of the receiver's `T` rows — silently doubling the visible log for that kill on every receiver. EP/GP totals were unaffected (the BATCH_EP receiver dedup caught the EP side), but the log was wrong.
- `AwardEP` and `AssignGP` gain the optional `atTime` parameter; existing 5-arg callers continue to work unchanged.

**Attendance.lua / DB.lua — every boss kill caused a wasted FULL-sync request**
- The leader's `AwardEP` loop bumped `dbRev` per call (25 times for a 25-player kill) while the receiver-side BATCH_EP handler bumps once. The leader was always 24 revs ahead after every boss kill, so every receiver's heartbeat saw "leader is ahead → request a FULL." `AwardEP` gains an optional `noBump` flag; `OnBossKilled` / `OnBossWipe` / `MassAwardEP` pass it on each loop iteration and call `Bump()` exactly once at the end, putting sender and receivers at the same `dbRev` after the broadcast applies.

**Attendance.lua — `MassAwardEP` (`/vcl award all`) used N per-player DELTAs**
- The mass-award command looped 25 individual `BroadcastDelta` messages — exactly the per-player flood that `BATCH_EP` was created to prevent. Each was queued at 50 ms so it stayed under the per-second ceiling, but it was wasteful (25 chunks vs 1) and queued behind any concurrent FULL transfer. Now uses a single `BATCH_EP` broadcast.

### Disconnect-Risk Audit — Boss Kill Path

A 25-player boss-kill mass-award produces, on the leader's wire, **two outgoing messages total**:
1. One `RAID/KILL` (small, ~1 chunk)
2. One `BATCH_EP` (small, ~1 chunk)

Both go through the existing 50 ms ticker so they're paced at 20 chunks/sec. Total channel time used by the leader: ~100 ms. Receivers process locally and do **not** broadcast in response — the `seenKeys` dedup in the BATCH_EP handler also catches any redelivered echo. Combined with this version's "Bump once" fix above, no follow-on FULL request is triggered either. **No 25-player flood. No per-player burst. No DC vector.**

### Version

- Bumped from **1.2.2** to **1.2.3** — boss-kill correctness fixes.

---

## 1.2.2 — May 8, 2026

### New SavedVariable — `VCLootProfilesDB`

Per-player gear, wishlist, main spec, off spec, off-spec ready flag, and class are now stored in a separate SavedVariable that is **decoupled from the EPGP database**. It survives `/vcl reset`, season bumps received over the wire, and any other path that wipes `VCLootDB`. On first load after upgrade, profile fields are migrated automatically out of the existing roster — there is no manual step.

The roster record continues to mirror the same fields, so every existing read site in `UI.lua` (and elsewhere) keeps working without changes.

### Bug Fixes — Gear / Wishlist Sync

**Sync.lua — PROFILE handler dropped data when the player had no roster row**
- Players who had never been awarded EP had no roster record; their incoming `PROFILE` broadcasts were silently discarded forever. The handler now writes to `VCLootProfilesDB` unconditionally and only mirrors onto the roster record if one exists. Their gear and wishlist are now visible from the moment the first broadcast lands.

**DB.lua — gear and wishlist were destroyed by `/vcl reset`**
- `ResetPreserveProfiles` snapshot only msRole / osRole / osRaidReady / wishList in memory, but the season bump it triggered caused every receiver's FULL handler to *hard-wipe* the entire roster — including their own local profile fields, which the broadcasting officer never had. Profile data now lives in `VCLootProfilesDB` (separate SavedVariable) and is re-applied to recreated roster records both locally (`HydrateRosterFromProfiles`) and on receipt of a season-bump FULL (`RestoreAllProfilesToRoster`).

**Sync.lua — `BroadcastProfile` cooldown was 60 seconds**
- Spec changes, wishlist edits, and gear swaps appeared stale on peers for nearly a full minute. Reduced to 15 seconds. The 2-second debounce in front of it still collapses rapid bursts (e.g. spec dropdown clicks) into a single send.

### Sync Improvements — Faster, More Complete Profile Propagation

**Sync.lua / Core.lua**
- `GROUP_ROSTER_UPDATE` (joining a raid, someone joining the raid you're in) now re-broadcasts your own profile after a 2-6 s random stagger, so a player who just joined sees existing members' gear / wishlist / spec within seconds rather than waiting for each peer to manually trigger a change.
- `PLAYER_EQUIPMENT_CHANGED` is now hooked: gear swaps refresh the local snapshot and broadcast (subject to the same 15 s cooldown), so peers see your current gear without you having to open the Profile tab.
- `Sync:RebroadcastOwnProfile()` exposed for any future callers that want to push the local player's profile on demand.

**Sync.lua — FULL payload now includes a PROFILES bundle**
- A new `---PROFILES---` section ships every known player's class / spec / wishlist / gear so that a fresh client (new guildmate, returning member, leveling alt) learns the full profile picture from a single FULL response. Previously profiles only propagated as players logged in and broadcast individually, so a new joiner could go weeks without seeing some members' data. Pre-1.2.2 senders simply omit the section and 1.2.2+ receivers fall back gracefully.
- The bundle is capped at the 30 most-recently-updated profiles (`PROFILES_SYNC_MAX`) so the FULL payload stays inside WoW's safe per-client outgoing-quota envelope. Anything past the cap continues to propagate via per-player PROFILE broadcasts. A belt-and-suspenders hard guard (`FULL_SIZE_DC_LIMIT`) drops the bundle entirely if the assembled payload would exceed ~160 KB / ~37 s of sustained sending — receivers fall back to per-player PROFILE messages.
- The season-bump branch in the FULL handler now calls `RestoreAllProfilesToRoster` after the hard wipe, so receivers' own gear and wishlist re-appear on the freshly-rebuilt roster instead of being permanently destroyed.

### Disconnect-Risk Audit

A full pass over the v1.2.0 / 1.2.1 / 1.2.2 changes confirmed every code path that emits addon messages still routes through the existing 50 ms ticker (`enqueueSend`) at 20 chunks/sec. No change increases per-client *outgoing* rate above the pre-existing safe ceiling:

- `PROFILE_COOLDOWN_S` (60 → 15 s) caps a single client to ~7-8 chunks per 15 s = 0.5 chunks/sec average.
- `GROUP_ROSTER_UPDATE` and `PLAYER_EQUIPMENT_CHANGED` re-broadcasts are fronted by the same 2 s debounce + 15 s cooldown, so bursts collapse to one send.
- Cancel/Disenchant `ROLL_END` adds a single small message per cancellation (rare).
- The FULL `PROFILES` bundle's length is bounded by `PROFILES_SYNC_MAX` and the hard guard above; the per-second send rate is unchanged.

Net effect: same safety profile as 1.1.x, with stricter caps in the new code paths.

### Version

- Bumped from **1.2.1** to **1.2.2** — profile decoupling + gear/wishlist sync hardening.

---

## 1.2.1 — May 8, 2026

### Bug Fixes — Roll Window

**Loot.lua — viewers' roll window not popping up**
- Fixed stale viewer sessions silently blocking new ROLL_START broadcasts. When a leader hit Cancel or Disenchant on a roll, viewers' windows stayed open until a 30-second auto-close timer fired. Any new ROLL_START during that window was silently dropped because `OnRollStartBroadcast` did `if hasActiveSession() then return end`. The handler now tears down a stale **viewer** session and accepts the new broadcast (a local **leader** session is still preserved so two concurrent officers don't clobber each other).
- Cancel and Disenchant now broadcast a roll-end signal (empty winner) so every viewer's window closes immediately. Previously viewers waited up to 30 seconds for their auto-close fallback.
- Fixed `OnRollStartBroadcast`'s auto-close timer math going negative when `endTime` arrived in the past (clock skew, message delay), which made `C_Timer.After` fire instantly and dismissed the window viewers had just opened. The remaining-seconds calculation is now clamped to a non-negative minimum.
- `OnRollEndBroadcast` no longer prints a "X won Y for 0 GP" line when the payload is the cancellation sentinel.

**Sync.lua — sender-authority cold-start miss**
- Fixed the rank cache returning nil for legitimate officer broadcasts in the first ~10 seconds after `PLAYER_LOGIN`, before the guild roster has arrived from the server. `getSenderRank` now force-refreshes once when the cache is entirely empty. `Core.lua` also pre-warms the roster at login.

### Feature

**UI.lua — Pass keeps the window open for the loot master**
- The Pass button on the roll window now behaves differently by role: viewers (regular raiders) close the window as before; **leaders / officers keep the window open** so they can still pick a winner from the leaderboard. Either way the local roll buttons are locked so the player can't accidentally roll after passing. The status label updates to "Passed — pick a winner from the list" for the leader's reference.

### Other Loot-System Fixes

- `OnOpenMasterLootList` now uses `canRunLoot()` (officer **or** group leader) instead of the stricter `isLeader()`. A non-officer designated Master Looter can now have the roll window auto-open on master-loot, matching the behaviour of `/vcl roll` and the alt-click path.
- `StartRoll` now warns the leader when their own sync is disabled while in a group, so they immediately understand why no one else is seeing the roll window.

### Version

- Bumped from **1.2.0** to **1.2.1** — roll-window fixes + Pass-button feature.

---

## 1.2.0 — May 5, 2026

### Security

**Sync.lua — sender-authority gate on every mutating message**
- `DELTA`, `BATCH_EP`, `RAID`, `SETTINGS`, `HIST_DEL`, `ROLL_START`, `ROLL_END`, and `FULL` (including the season-bump hard-wipe path) are now rejected unless the sender holds Vibe Master / Officer / Officer Alt / Loot Master rank, OR is the in-game Master Looter, OR (for leader-or-officer messages) is the current group leader. Previously any guild member could broadcast a forged `DELTA` to award arbitrary EP/GP, push a forged `SETTINGS` to corrupt every roster's GP formula, or wipe every receiver's database via a forged season bump. Guild ranks are looked up via a 30-second cached `GetGuildRosterInfo` table.
- `PROFILE` messages now require the in-payload player name to match the sender. Without this, anyone could overwrite anyone else's role, gear snapshot, and wishlist on every client.
- Numeric `SETTINGS` values are clamped to per-key sane bounds (`gpFinalMult` 0.0001–1000, `decayPercent` 0–100, etc.) so an out-of-range push (or `inf`/NaN) cannot land. `BATCH_EP` and `DECAY DELTA` amounts are clamped to sane upper bounds (≤100 000 EP per kill; 0–100 % decay).
- Only officers reply to `REQ FULL`; non-officer replies would be rejected on receive anyway, and skipping the send saves bandwidth and reduces flood risk.

### Bug Fixes

**Sync.lua**
- Fixed lifetime EP/GP double-count on offline-divergence reconciliation. The old code adopted the sender's `lifeEP` via `max(local, incoming)` and then *also* added local-unique entries' amounts on top, compounding lifetime totals every time a FULL sync triggered the replay path. `applyHistoryEntries` now takes a `replayMode` flag that skips the lifetime bump.
- Fixed `mergeHistory` truncating local history to 1 000 rows when the local DB cap is 2 000. Receiving a FULL no longer silently drops rows the local SavedVariables would otherwise keep — both ends now reference `ns.C.MAX_HISTORY`.
- Fixed `histKey` collisions when two officers awarded the same EP amount to the same player in the same wall-clock second. The dedup key now includes `by`. A `legacyHistKey` fallback keeps pre-1.2 tombstones working during the transition window.
- Fixed `BATCH_EP` re-application on redelivery. Receivers now build a `seenKeys` set from current history and skip rows that already exist, so a redelivered or twice-fired BATCH_EP does not double-credit attendees.
- Fixed `DELTA` re-application on redelivery: receivers now check the canonical histKey against existing history before applying.
- Fixed chunked-transfer expiry being measured from the *first* chunk's wall-clock arrival rather than last activity. A FULL exceeding ~600 chunks (~140 KB) was silently discarded mid-stream. Expiry is now last-activity-based and the window is raised from 30 s to 60 s.
- Fixed self-echo guard's short-name comparison dropping legitimate messages from a same-realm group member who shares a first name with the local player. Comparison now uses normalized full names.
- Fixed `CHUNK_SIZE = 230` cutting close to the 255-byte addon-message cap (worst-case framed header for `BATCH_EP` could push the total over). Reduced to 215 with explicit header-size accounting documented inline.
- Fixed `hasLocalUnique` rev-bump being set against the *unfiltered* local-unique list. Tombstoned entries that had already been deleted elsewhere were counted as new data we needed to advertise, causing peers to needlessly request a FULL from us. Now reflects the post-tombstone-filter count.
- Fixed unbounded growth of `presence` / `inbox` / `reqLastSent` when `syncEnabled = false` (Heartbeat never runs and never prunes). A throttled `maybePrune` runs from `OnMessage` every 60 s.

**Sync.lua / DB.lua / UI.lua — history deletion now reverses EP/GP**
- Centralized `DB:DeleteHistoryEntry(idx)` reverses the entry's EP/GP impact on the roster, drops the row, tombstones the canonical histKey, and bumps `dbRev` in one call. Both the UI right-click delete and the sync `HIST_DEL` handler use it. Previously the row was removed from the visible log but the points it awarded stayed on the player. `applyTombstones` (FULL receipt) now also reverses EP/GP for tombstoned rows.

**Slash.lua — `/vcl sync` no longer floods the guild**
- `/vcl sync` now uses the targeted-best-peer flow (whisper exactly one peer for a FULL response) instead of broadcasting `REQ FULL` to GUILD where every online addon user simultaneously responds. If no peers are known (cold start) it fires a heartbeat, waits 3 s for replies, then falls back. `/vcl sync force` skips the wait and broadcasts immediately.

**Decay.lua**
- Fixed `decayBaseGP = false` (the "protect base GP" mode) silently doing nothing because it referenced `s.baseGP`, but the actual setting key is `s.gpBase`. The floor protection has never engaged since the GP-formula migration; it works correctly now.

**Attendance.lua**
- Wipe-EP awards now use one `BATCH_EP` message instead of N per-player `DELTA` messages, matching the boss-kill flow. This both cuts traffic and lets non-officer raid leaders broadcast wipe credit (DELTA is officer-only post-1.2; BATCH_EP is leader-or-officer).

### Version

- Bumped from **1.1.0** to **1.2.0** — sync hardening + security gates.

---

## 1.0.0 — April 29, 2026

### Bug Fixes

**Loot.lua / Core.lua**
- Fixed double `HandleModifiedItemClick` hook. When Bag Roll modifier was set to Alt (the default), both the Core.lua hook (`Loot:OnAltClick`) and the UI.lua bag-roll hook fired on the same alt-click, opening the roll prompt twice. `OnAltClick` now yields when the bag roll modifier already covers that gesture.

**UI.lua**
- Fixed free roll auto-detection using `GetNumGroupMembers()`. A 25-man raid with fewer than 20 people online would falsely trigger the 10-man free roll default. Detection now uses `maxPlayers` from `GetInstanceInfo()`, which returns the instance's hard cap (10 for Kara/ZA, 25 for 25-man content), and is therefore always accurate regardless of current attendance.
- Fixed free roll checkbox state not being passed correctly to `StartRoll` when unchecked in a 10-man instance. The checkbox visual updated correctly but the state read by the start button could be stale. State is now snapshotted into a local before the prompt frame is hidden.
- Fixed multiple Unicode characters rendering as blank squares in-game: em-dash (`—`) and middle-dot (`·`) in `SetText`, `AddLine`, and `ns.Print` calls across UI.lua and Backup.lua replaced with ASCII equivalents.
- Fixed the wish list "Done" button displaying `←` and `—` as blank boxes; replaced with `<<` and `-`.

**WishListData.lua**
- Fixed item IDs and boss assignments for Zul'Aman, Hyjal Summit, Black Temple, and Sunwell Plateau. Nearly all entries across these four raids had incorrect item IDs, wrong item names, or were assigned to the wrong boss. All four raids have been rewritten with correct Wowhead-verified data including tier token assignments (Azgalor = T6 gloves, Archimonde = T6 helms, Mother Shahraz = T6 shoulders, Illidari Council = T6 legs, Illidan = T6 chest, Kalecgos = SWP bracers, Brutallus = SWP belts, Felmyst = SWP boots, Eredar Twins = SWP necks, M'uru = SWP rings, Kil'jaeden = SWP chest).

---

### New Features

**Sync.lua — Tombstone System**
- History deletions are now durable across syncs. When an officer deletes a history entry, its key is recorded in a tombstone set (`db.deletedHistKeys`) with a timestamp. Full sync payloads include a `---TOMBSTONES---` section; receiving clients apply incoming tombstones after merging history, permanently removing any re-introduced entries. Tombstones expire after 90 days and are pruned on send and receive to prevent unbounded growth.

**UI.lua — Bag Roll Modifier**
- Added a configurable modifier key for clicking bag items to open the roll session prompt. Configured in Settings → Loot Distribution → Bag Roll. Options: None (disabled), Alt-click, Ctrl-click, or Alt+Ctrl-click. Replaces the previous hardcoded alt-click behavior with an explicit, user-controlled setting.

**UI.lua — Addon Hotkey Relocated**
- The keyboard shortcut for opening/closing the VibeCheck window has been moved from the Loot Distribution tab to the Sync & Data tab, which is a more appropriate home for a UI preference that is not related to loot settings.

**UI.lua — Free Roll Auto-Check for 10-Man**
- The Free Roll checkbox in the roll-start prompt is now automatically pre-checked when you are inside a 10-man instance (Karazhan or Zul'Aman), defaulting to the no-EPGP roll mode appropriate for those raids. The checkbox remains interactive — officers can uncheck it to run a normal EPGP roll if needed.

---

### Version

- Bumped from 0.9.5 to **1.0.0** — first full public release.

---

## 0.6.0

### Bug Fixes

**Decay.lua**
- Fixed incorrect default decay day-of-week. The fallback was `2` (Monday) but the setting default in Constants is `3` (Tuesday). Decay now fires on the correct night for guilds who never changed this setting.
- Fixed incorrect default decay hour. The fallback was `6` (6 AM) but the setting default is `0` (midnight). Both defaults now match Constants.DEFAULTS.

**Core.lua / Loot.lua**
- Fixed encapsulation bug where `Core.lua` was directly writing `ns.Loot.tradeBothAccepted = false` after `TRADE_CLOSED`. The flag is now read and reset entirely inside `Loot:OnTradeClosed()`. Core just calls the method with no arguments.

**Attendance.lua**
- Fixed `benchEPMultiplier` not being bounds-checked. A value above 1.0 would award bench players more EP than raiders. The multiplier is now clamped to `[0, 1]` before use.

**Sync.lua**
- Fixed SYNCABLE_SETTINGS guard on the *receive* side. Previously, unknown keys from a newer client version could be written into settings. Settings received over sync are now validated against the explicit whitelist before being applied.

---

### New Features

**Sync — Alt Map Propagation**
- Full sync payloads now include an `---ALTS---` section. Alt links set by any officer will now propagate to all addon users on the next full sync. Incoming links are merged non-destructively (conflicts are skipped rather than overwritten).

**Sync — Last-Sync Status Indicator**
- The addon now tracks `Sync.lastSyncTime` and `Sync.lastSyncSender` whenever a full sync is applied.
- The Settings → Sync & Data sub-tab shows a live status line: "Last sync: 3m ago from Playername" or "Not yet synced this session."

**WishListData — Phase 3–5 Content**
- Added complete wish list item databases for all remaining TBC phases:
  - **Zul'Aman** (6 bosses: Nalorakk, Akil'zon, Jan'alai, Halazzi, Hex Lord Malacrass, Zul'jin)
  - **Hyjal Summit** (5 bosses: Rage Winterchill, Anetheron, Kaz'rogal, Azgalor, Archimonde) — includes all T6 token slots
  - **Black Temple** (9 bosses: Naj'entus through Illidan) — includes both Warglaives
  - **Sunwell Plateau** (6 bosses: Kalecgos through Kil'jaeden) — includes Thori'dal

**Constants — Zul'Aman Boss List**
- Added all 6 Zul'Aman NPC IDs to `BOSS_LIST` for completeness, consistent with the existing Karazhan entries. Kill detection is still filtered to 20+ man raids so no EP will auto-award from ZA — the entries are informational.

**Settings — CSV Export**
- The "Export CSV" button in Settings → Sync & Data now opens an in-game copy dialog with the full roster CSV (sorted alphabetically), instead of dumping each line to chat. Copy and paste directly into a spreadsheet.

---

### Removals (approved)

**Slash Commands**
- Removed `/vcl test roll` — seeded fake roll sessions for UI testing. No longer needed now that the roll window is stable.
- Removed `/vcl test raid` — injected fake Gruul + Magtheridon raids. Removed alongside the 140-line injection block in `Slash.lua`.
- Removed `/vcl export` — chat-line CSV dump. Replaced by the in-game copy dialog in the Settings tab.

**Loot.lua**
- Removed `StartTestRoll()`, `TEST_ITEMS`, and `FAKE_RAIDERS` (~90 lines of test scaffolding).

---

### Performance / Quality

**Sync — History Cap**
- `HISTORY_SYNC_MAX` raised from 200 to 500 entries in outbound FULL payloads.
- Local post-merge history cap raised from `HISTORY_SYNC_MAX * 2` (400) to a fixed 1000 entries, matching the UI's display limit.

---

## 0.5.0

- Standings tab: complete rewrite with role filter pills (All / Tank / Healer / Melee / Ranged), PR rank column, attendance color coding, and Shift+Click double-sort on any column.
- Log tab: Raids sub-tab with zone filter pills, duration column, and pulsing "In progress" row. History sub-tab with date-range filter (All / Today / Week / Month), clickable player name filter, and totals footer.
- Fixed inline search-bar clear button (X) misalignment in Standings.
- Fixed "0 PR" filter button labels — now "Show 0 PR" / "Hide 0 PR".
- Standings sort now defaults to PR descending. Shift+Click any column header to set a secondary sort key.
- Fixed Unicode characters rendering as blank squares in-game. All rendered strings now use ASCII-only substitutes.

## 0.4.x and earlier

See git history.