DopingControl 0.7.0

This commit is contained in:
2026-10-15 12:29:40 +02:00
commit e6561d3bce
48 changed files with 19254 additions and 0 deletions
+406
View File
@@ -0,0 +1,406 @@
# Changelog
All notable changes to DopingControl. Versions are the ones actually
released here — a new feature raises the middle number and resets the last
one, the last number is for fixes without new features.
## v0.7.0
### New Addons tab: who in the raid runs BigWigs, Threat Meter, EPGP, SuperWoW, Rinse
A seventh tab checks the raid's addons without the other raiders needing
DopingControl. Each column is detected only through what that addon sends or
answers: BigWigs' version query, the TW Threatmeter "who" query, shootyepgp's
version announcement and its SuperWoW reply, and UpsiRinse's version query.
* **Version in every cell.** Green with a check mark = the newest version in
the raid (your own row counts); green with an exclamation mark = present but
older (the tooltip names the newest); red = no reply after the query
window. Versions compare numerically per segment, and only within the same
BigWigs fork.
* **Honest silence.** Red only after a query window passed; before any query a
cell is grey "?". Not-in-guild (EPGP/SuperWoW) and non-dispeller classes
(Rinse) are "n/a", not red. The red tooltip says what it really means: not
installed, an old version, or a fork that does not answer.
* **Check addons** button; opening the tab runs one automatic check, at most
every 30 seconds, never in combat and never on joining a raid. A passive
listener records versions that addons announce on their own, and forgets
raiders who left.
* Your own row is read from your own addon list instead of waiting for replies.
* `/dc test` shows a deterministic mix of states on this tab.
* This adds a new file (`scan/addons.lua`), so the game must be restarted
once, not just reloaded.
### Equipment tab names the enchant, not just "enchanted"
Hovering an Equipment cell used to say only "Enchanted" or "Not
enchanted". It now shows which permanent enchant the slot carries
("Enchant: Crusader", "Enchant: Agility +15", ...). The names come from a
table generated from the game client's own enchant data, so they match
what the client shows, including server-custom enchants. An enchant that
is not in the table shows as "enchant #<id>": it is still known to be
enchanted, only the name is unknown, and it is never reported as missing.
### The addon does the pre-raid test-mode check itself
`/dc test` is persisted, so it survives a reload and a relog. A session
that ended with the simulated raid on screen started the next one showing
it again — and from then on the matrix graded a roster that does not
exist. On screen the only marker was the source banner, so the failure
mode was never an error, just a plausible wrong answer. That is the worst
kind for a buff checker, and "remember to check before the raid" is not a
routine anyone keeps.
Now the addon says it, at the two moments it matters:
* **Once per login or reload** — printed on world entry, not earlier;
chat output before that point is silently dropped.
* **When a group forms** — on the 0 → grouped edge only, so a raid
filling up prints it once, not once per member.
Both lines name the mode that is actually on and the switch that leaves
it (`/dc test` or `/dc demo`) — naming the wrong one would send you to a
toggle that changes nothing.
### "readable" means one thing now — the footer and the tooltips caught up
v0.6.1 gave the coverage pill its channel nouns (`15/26 hit read`), but the
footer strip on the Hit and Resistances tabs still said **AVERAGE
(READABLE)**, the chat report still said "average, readable players", and
the empty-slot tooltips said "unit readable" — all naming the gear/hit/
resist read while the word elsewhere (the `Not readable` list, the
`N unreadable` chips, the `N/N readable` group chip) means "this player
could be read at all". Same word, two populations, on one screen.
Decided and pinned in a code comment (`core/model.lua`, `CoverageLabel`):
**"readable" = the player could be read this scan (online, auras
answered)** — and nothing else. Every channel-specific population is
named by its channel: `gear read`, `hit read`, `resist read`.
* Footer on Hit / Resistances: **AVERAGE (HIT READ)** / **AVERAGE (RESIST
READ)** — derived from the same function as the pill, so the two can
no longer drift apart.
* Chat report section headers: `Hit (average, hit-read players):` /
`Resistances (average, resist-read players):`.
* Empty-slot tooltips: `gear read, no trinket equipped …`, `gear read, no
item equipped …`; the generic "slot empty" line takes the tab's noun
(`readable` on the aura tabs, `gear read` on Equipment).
* Coverage-pill tooltip on gear/hit/resist tabs now leads with the pill's
own number and noun (`15 of 26 hit read`), then the row-readability
line (`26 of 26 raid members readable (online, auras answered)`) —
before, the pill said `15/26` and its tooltip `26 of 26`.
* Two more spots missed the first pass: the gear-unreadable row's status
chip and its row-spanning explanation (`ui/matrix.lua` `UnreadRowText`,
`core/model.lua` `row.unreadNote`) said `items not readable` — right
next to a group header that counts that same row as readable (only its
EQUIPMENT channel came back empty). Both now say `gear not read`.
## v0.6.4
### Equipment expectation top-up now self-heals every refresh, not just once at ADDON_LOADED
Field report after v0.6.3 shipped (WAIST seeded `true` for every role):
after deploying and reloading, the Equipment matrix still showed 16
columns (no WAI) and the owner's own row read "(read failed)" / sum "--".
Offline reproduction against the real captured dump
(`dc_raid_1788070493.txt`, the scan taken right after the reload) and the
real live `db.expectations` shape
(`WTF\...\SavedVariables\DopingControl.lua`, all 20 role/class lines —
`NECK`/`R1`/`R2 = true`, `WAIST` absent everywhere) through the actual
`DC_Gear.Assemble` -> `DC_Model.build` -> `DC_Matrix` pipeline found no
Lua error anywhere — no nil dereference, nothing caught by a `pcall`.
Both symptoms are real, and neither is what it first looked like:
* The "(read failed)"/"--" row is a correct, unrelated read: the dump's
own `P`/`H`/`X` lines already carry `aurasRead=0`/
`unreadableReason=read failed` before any render happens —
`scan/aura.lua`'s "zero USABLE auras this scan => aurasRead=false" rule
firing on a genuine zero-buff moment. Nothing to do with WAIST or
equipment.
* The missing WAIST column traces to `core/model.lua`'s `anyClassExpects`:
it only falls back to the `DC.DEFAULT_EXPECT` seed for a (role, class)
combo with NO saved line at all — an *existing* class line (any line
materialized before the v0.6.3 seed change) is trusted as complete, key
by key. `DC.EnsureExpectations`/`topUpLine` (`core/config.lua`) are
themselves correct — proven by running them against the exact real
saved shape above — but they only ran once, at `ADDON_LOADED`. A
`db.expectations` line materialized before v0.6.3 shipped therefore
stayed short the new key until a SECOND full reload happened to run
`EnsureExpectations` again against the current seed.
Fix: `ui/matrix.lua`'s `MX.ExpectTable` — the one resolver both the header
and `MX.Refresh` read the expectation table from — now defensively re-runs
`DC.EnsureExpectations(d)` every time it resolves, i.e. on every
scan/window redraw, not only once at boot. `topUpLine` only ever fills
`nil` keys, so this is idempotent and side-effect-free on an
already-current table; it self-heals a stale class line the moment the
window redraws, without requiring a second reload.
New regression test `tools/luatests/test_expectations_topup.lua`: embeds
the real captured `db.expectations` shape (all 20 role/class lines,
verbatim) and a real gear/hit dump line, reproduces the exact
16-column/no-WAI/`(read failed)` symptom through the unmodified
`DC_Model.build`, then proves `MX.ExpectTable` self-heals it to 17 columns
with WAIST at column 6.
## v0.6.3
### Waist is expected by default for every role
Owner decision, reversing the "confirmed, not a bug" call below: every role
wears an enchantable belt, so WAIST now belongs with the other worn
equipment slots (HEAD, CHST, WRST, ...) instead of the opt-in neck/ring
treatment. `data/expectations.lua`'s seed now carries `WAIST = true` for
all five roles. Existing SavedVariables pick up the flag automatically —
`core/config.lua`'s `topUpLine` already adds any seed slot the saved line
has never seen (`nil`) as `true` on load, the same mechanism that
delivered the neck/ring columns to existing saves; no separate migration
was needed. An explicit `WAIST = false` set for a role/class still hides
the column, same opt-out behavior as any other slot.
### Confirmed: the waist column's off-by-default is intended, not a bug
A field report said the belt was worn but the Equipment tab still showed
16 columns, no WAI, after the v0.6.2 waist-slot fix was deployed and the
UI reloaded. Traced end to end (`core/model.lua` `tabSlots`/
`anyClassExpects`, `ui/matrix.lua`'s `vm.slots` column build, the live
SavedVariables `expectations` table): the gating is working exactly as
v0.6.2 shipped and tested it — the waist enchant column is opt-in per
role/class, the same treatment as neck/rings, and simply had not been
enabled for any role/class yet (unlike neck/rings, which already carry
`= true` in every class line of the reporting player's save from before
this default existed). No code was hiding anything; the column appears
the moment the checkbox is enabled in Options > Equipment.
Added a regression test (`tools/luatests/test_model.lua`, section 8d)
that exercises the real production path — `core/model.lua`'s `M.build`
through `tabSlots`/`anyClassExpects`, the same function `ui/matrix.lua`
renders from — instead of the static `DC.SLOTS_EQUIPMENT` array the
layout test uses. It pins both halves: WAIST absent from `vm.slots`
under the default seed, and present + classifying correctly (HAS/
MISSING/UNKNOWN) once a role/class expectation is switched on. This
closes the gap that let the "is it really gated correctly" question go
unanswered by the existing suite.
## v0.6.2
A fix release.
### The waist slot was never scanned
No equipment read — the raid dump, the Equipment tab, the Hit tab's
belt contribution — ever covered the waist (inventory slot 6). The slot
list that drives every `GetInventoryItemLink` read was borrowed from the
Equipment tab's enchant-tracking column list, which had no waist column;
that list never included the waist at all, so it was silently skipped in
every read that reused it, not only display.
The waist turns out to be enchantable on this server (a custom "Belt
Buckle" item line, the same kind of addition that already made the neck
and both rings enchantable beyond stock behaviour), so it now gets its
own Equipment-tab column — off by default, like the neck and ring
columns, until enabled per role/class in the options grid.
## v0.6.1
A fix release. Everything here is something that was supposed to work
already — most of it found by running the addon in a real 26-man raid and
then checking what it had actually read.
### Resistances of other players are readable at last
The Resistances tab shipped in v0.6.0 and showed `?` for everyone but you.
That was not a display bug: this client is never told other players'
resistances. Both read paths return `0` for a raid member standing next to
you, fully loaded — including their armour value, which is the giveaway
that the field is simply not transmitted rather than genuinely zero.
The numbers are now **reconstructed from the equipped items' tooltips** —
the same route the Hit tab already takes — and `Equip: +N All Resistances`
counts on all five schools. On top of that comes the **racial bonus**,
which is written in no item: Dwarf +10 Frost, Gnome +10 Arcane, Night Elf
and Tauren +10 Nature, Undead +10 Shadow. Goblin and High Elf get nothing
added, because no resistance racial for them could be confirmed — an
invented bonus would be worse than a missing one.
This stays a near-range reading. A raid member the client has not loaded
has no readable equipment at all, and their column stays `?`.
### The weapon-buff column could never report a missing buff
For another player, the client returns *nothing* for the weapon
enchantment when there is no imbue — not an empty string, as the code
expected. So everyone without a weapon buff was shown as `?` instead of as
a gap, and was left out of the gap count, the footer and the whisper.
Measured in a 26-man raid: **14 of 25** players were mislabelled that way —
precisely the case a buff checker exists to report.
The fix distinguishes by readability: if the main hand can be read and
there is no imbue, that is a gap. If the player cannot be read, it stays
unknown. Unknown is still never silently turned into missing.
### Consumables that were being discarded
Six buffs were tracked in the tables but never recognised, because the
aura carries a different name than the item: **Elixir of the Giants**,
**Elixir of Greater Agility**, **Elixir of Fortitude**, **Strike of the
Scorpok**, **Rage of Ages** and **Elixir of Greater Frost Power**. In one
raid, `Elixir of the Giants` alone was thrown away 38 times.
Aura names also arrive from the client with trailing spaces on occasion,
which defeated the name match; they are trimmed now.
### "Gaps only" no longer empties Hit and Resistances
Those two tabs are numeric — no cell there can be *missing*, so the filter
hid every row. It now also counts the unknown cells, which is what there is
to see on those tabs.
### The coverage number belongs to the tab it is shown on
The pill counted the players whose **auras** could be read — and showed
that number on every tab. Auras are readable raid-wide, equipment and hit
only nearby, resistances only for yourself. In the same raid the pill
claimed `26/26` while the Hit tab really knew 15 of 26 and the Resistances
tab 1 of 26.
Each tab now reports its own reading, and says which one it is:
`26/26 readable` on the aura tabs, `15/26 gear read`, `15/26 hit read`,
`1/26 resist read`. The report line in chat carries the same number. As a
side effect the word "readable" now means one thing instead of two.
## v0.6.0
### Hit
A new **Hit** tab, and with it the addon's second numeric tab after
Resistances. Eight columns: melee, ranged, and one per spell school —
because talents and items raise schools individually, so one "spell hit"
number would be wrong for most casters.
There is **no hit API in 1.12**. The number is assembled from what can
actually be read:
- **items** — parsed out of the equipped items' tooltips
- **set bonuses** — counted **once per set**, only when the piece
threshold is met (the bonus line is printed on every piece of the set,
so adding it per item would multiply it)
- **hit auras** — buffs that grant hit
- **talents** — for your own character, and for other players over the
inspect protocol this server ships (see below)
- **weapon skill** — every point above 300 is worth 0.2% hit against a
+3-level boss
The cell tooltip shows that as a plain **addition**: one row per source,
values in a right-aligned column, so the total can be checked by adding it
up. Sources that contribute nothing are still listed at `+0.0%` — a missing
row would look like a forgotten source. A source that could not be **read**
shows `?`, never `+0.0%`: unknown is not zero.
The boss's requirement is stated as one flat number (`Cap vs a +3-level
boss`). It does not move with your weapon skill — skill counts on the
player's side of the comparison, where it belongs.
Caps: melee 8% at 300 weapon skill, dual-wield white swings 27%, spell 16%.
A cell at or above its cap is marked green.
### Reading other players' talents
Talents raise hit by up to several percent, so a hit number without them is
systematically low. WoW 1.12 has no foreign-talent channel and this server
transmits none either — but it ships **its own inspect protocol**, a plain
addon-message conversation: we ask by name, the target's client answers with
its talent tree.
DopingControl uses exactly that, for the players the scan already shows.
One request per player, at most one every five seconds, and each name at
most once per half hour. It can be switched off entirely with
`/dc talents off`.
### Weapon skill books
A quest weapon-skill book grants +3 skill — about 0.6% hit — and **cannot be
detected on another player**: no channel reports foreign weapon skills. So
it is entered by hand, in a new **Skill books** options tab: a grid with one
row per scanned player and one column per weapon skill, with a checkbox
wherever that class could own a book for it.
### Whisper on the Hit tab
The hit tab now offers the per-player whisper button, unlike the other
display tab: being short of the hit cap is something that player can fix,
a resistance total is not. The whisper carries both numbers —
`Melee hit 5.0/8.0` — so the reader knows the distance, not just the fact.
It only ever mentions the columns that **role** is asked about: a melee
player is never told about his shadow hit, and healers are never whispered
at all (heals cannot miss). Every column stays visible with its own cap
marking on the tab — the display hides nothing, only the whisper is
selective.
### Class buffs
A sixth slot: **Emerald Blessing**. It ships with no expectation default —
it is a raid quest reward almost nobody carries — so its column stays
hidden until you enable it per role and class. Its +1% spell hit counts on
the Hit tab either way.
### Tables
- **Alternating row shading** in every table (main window, both options
grids), so a row can be followed across a wide grid.
- The **options window width follows the selected tab** instead of being
fixed to the widest one.
- The options column headers now use the **same header tiles as the main
window** — three-letter label over a sub-line, in a bordered strip.
### Fixed
- **Could not scroll to the bottom** of the main window once every role
group was expanded. The content height was right; the scrollbar was
never told about it, so it kept the maximum it had when the group was
still collapsed.
- An unreadable equipment row was labelled **"out of range"** even when
distance was not the cause. It now states what was actually observed —
that no equipment slot returned an item link — and prints the measured
distance separately.
- **"Not expected for role X"** was shown for cells decided by class or by
the item (a shaman has no ranged enchant slot; an empty exemptable slot),
and pointed at an options switch that cannot change them. Those cells now
give the real reason.
- The hit-column header claimed the number was summed from items alone,
while it also contained auras and talents.
### Test mode
`/dc test` now simulates a **40-player raid** in eight subgroups instead of
25 players, including deliberately unreadable players and a completely
unreadable group.
## v0.5.2
New **Resistances tab**: display-only totals for all five schools per
player with a readable-players raid average in the footer. Equipment
semantics finalized: an empty slot counts as a gap unless it is one that may
legitimately be empty (off hand, ranged), the expectation grid governs
enchants only, and neck/rings joined the enchant logic (enchantable
on this server; expectation defaults to off). Expectation storage now
distinguishes a deliberate uncheck from a never-seen slot, so slots added in
future versions appear automatically.
## v0.5.1
The Equipment tab now covers all sixteen slots: neck and both rings joined
as item tiles (quality border, tooltip, empty slot counts as a gap — no
enchant exists for them in vanilla, so they carry no corner marker).
## v0.5.0
First public release: the four-tab readiness matrix (Consumables / Class
buffs / Debuffs / Equipment), the HAS–MISSING–UNKNOWN honesty model with
coverage pill and last-known display, the five-role system with
auto-suggestion and per-character confirmation, the role × class
expectations grid, per-player whispers with cooldown, the raid report with
mandatory preview, ready-check and window-open scan automation, and the
deterministic test mode.