e269e4dbb4091e1f8ce6fa045d00949901d0e749
15
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
02265cc224 |
compat: nampower's equipment and item-cache getters, for the character panel
"then we gotta fix bettercharacterpanel, that one is driving me nuts."
BetterCharacterPanel is built on five nampower getters and had none of them.
`GetEquippedItem` was 8 raises in one session of his log; the other four failed
inside the addon's own `pcall`s, so its item-level column, quality colours,
missing-enchant warning and two-hand test were all dead at once. DoiteAuras
reads the same getter in six places.
The spec is the installed `nampower.dll` itself rather than inference, which is
what unblocked this — the earlier note here said to find the upstream spec
first, and the DLL *is* the spec:
- its usage strings give the signatures,
- "Slot must be a number between 1 and 19" gives the band, so the slots are
1.12's own `GetInventoryItemLink` numbering and `NampowerAPI.lua`'s
`slot - 1` in `IsItemInSlot` is that shim's bug, not the contract,
- its string pool gives the key names in order, and
- "Item not found in DBCache" is a raise, which is exactly why the addon
`pcall`s these three and calls `GetEquippedItem` bare.
`itemLevel` is the one field 1.12 cannot reach at all: `GetItemInfo`'s fourth
value is the *required* level, and later clients are the ones that insert
`itemLevel` there — so the panel's item-level column had no possible source
before this. The cache row now carries it, with `buyPrice` and `displayInfoID`
beside it, and an equipped item carries the raw descriptor numbers the tooltip's
resolved enchant lines could not answer (an id whose `SpellItemEnchantment` row
hides its name is still an id).
Fields left absent rather than guessed are listed in code and in
`docs/ADDON_COMPAT.md`; nothing installed reads any of them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
|
||
|
|
13989fb09b |
messageframe: a scroll that moves the view fires OnMessageScrollChanged
WIM sets this script on every window it builds — the history frame at load, then one per correspondent (six whisper windows in one session of his log) — and we refused the name by raise, which killed the rest of the builder function each time, not just the handler. It was on the deliberate "real 1.12 kinds nothing fires raise rather than silently accept" list. That rule is right and this was the wrong side of it: the slot now fires, from the ScrollingMessageFrame's own scroll verbs when a scroll actually moves the view, which is what a chat addon hangs its scroll-down arrow on. A scroll refused at either end moves nothing and says nothing; whether the reference fires one there is untraced. The raise list keeps its rule and its test — `OnInputLanguageChanged` and the movie frame's are still raises, and a name leaves that list only when something fires it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn |
||
|
|
15f069667e |
saved: a widget writes as its name, so a saved frame comes back a frame
Decursive raised `attempt to call method 'AddMessage' (a nil value)` on every
login (`Decursive.lua:1393`). It keeps its output window in its own saved table
and guards the reload with `if Dcr_Saved.Dcr_OutputWindow == nil or not ...`
(`:1245`) — which a non-nil *data* table passes.
Our writer walked the frame like any other table: his `Decursive.lua` came back
holding ChatFrame1's own fields (`buttonSide`, `channelList`, `editBox`, …),
twice the size of the real client's file and with no methods on it at all.
The reference tests the value's `T[0]` identity before its fields —
`lua_rawgeti(v, 0)` then `lua_touserdata`, `0x704613`-`0x704628`, reading the
lightuserdata `0x701bd0` wrote — and never walks a widget. A named one writes as
its **bare global name**, a Lua expression, so the reloaded chunk's own lookup
hands the live object back; an anonymous one as `nil --[[ unnamed object ]]`
(`0x872e0c`). His own real-client file is the proof:
["Dcr_OutputWindow"] = ChatFrame1,
Deviation: a name that is no Lua identifier writes as an anonymous object, where
the reference writes it raw and would leave a file that will not parse.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
|
||
|
|
68138334ed |
spell_fx: an effect on a host with no attachment points seats at its box top
Reported from play: "ore vein is still not sparkling" — so the switch fix landed and the sparkle still was not there. Reproduced offline in the `fxview` GameObject lane (`WOW_CAPTURE=fxview WOW_FX_GO=310 WOW_FX_GO_TYPE=3`, a copper mining node), which runs the real chain. Everything upstream works: the gate fires, `attach_spell_fx` spawns the instance (`kit spawn … Particles\LootFX.mdl`), and `WOW_PARTICLE_CENSUS` finds all four of LootFX's emitters at the node's position, live, ungated and drawn — while the captured frame is byte-identical to one taken with the sparkle gated off. Nothing was missing; nothing was visible. The attach cascade's last rung is the host's origin, and for a unit that is right: `0x13` is attachment 19, Base, which creature models author at the ground point under the feet, and the loot sparkle is a ground twinkle. A 1.12 GameObject model authors no attachment points at all (`copper_Miningnode_01.m2`: 0 — `benilla-m2`'s `dump_attach`), so the cascade fell through to the node's pivot, which is inside the rock: that model's own 397 vertices run y ∈ [-3.83, +2.73], and `Particles\LootFX.m2` is a ~1 yd twinkle (four emitters, 0.06–0.14 yd particles, ~0.9 yd reach) with nowhere to go. So a host with no attachment point seats the effect at the top centre of its own box. Not its middle — a node is modelled around its pivot, not above it, and this one's box centre is 0.55 yd underground, which capture-tested as invisible too, as did half the box height. At the box top the capture differs from the gated-off one; a peacebloom, whose model is small, already showed at the pivot. The placement is ours and says so: SuperWoW publishes no source and the reference has no object arm to copy. It stays subtle by design — row 14 of `SpellVisualEffectName` scales 1.0 — so it reads as a few twinkles over the node, not a beacon. If that is not what the real client shows him, the next lever is the effect's scale, and that one wants his eyes, not mine. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn |
||
|
|
ed92574cea |
tooltip: SetOwner hides the plate, so a rebuild gives the addon its OnShow back
Reported from play with a video: hovering a bag item draws the enhanced tooltip — MasterTradeSkills' recipe block, AdvancedTradeSkillWindow2's Bags/Bank/Total — for about nine frames of sixty, then the plate falls back to the bare one and stays bare while the cursor sits still. The loop is stock. `ContainerFrameItemButton_OnUpdate` re-enters `..._OnEnter` every `TOOLTIP_UPDATE_TIME` (`ContainerFrame.lua:693` in his own patch-4.mpq; pfUI's bag slots are `ContainerFrameItemButtonTemplate`, so they run it too), and each turn is `SetOwner` + `SetBagItem` — a clear that wipes every line an addon appended. What puts them back in the reference is a hide/show pair, and we had neither half: the `SetOwner` binding hides the plate before it even reads the owner argument (`0x531179` zeroes the content gate at `+0xd0`, `0x531183` calls `[vtable+0x84]` = `0x530a60`, which is the `SetOwner(NULL)` core plus the base hide `0x76ad50`), and the content `Set*` re-shows it through the tooltip's own Show override (`0x530a80`). So every rebuild raises `OnHide` then `OnShow`. Ours cleared in place, and the base show early-outs on an already-visible frame (`0x76ae3b`), so the plate never hid and the `OnShow` never came. That is why exactly two addons lost their lines while others kept theirs: `LibItemTooltip-1.0`, the tooltip contract of MasterTradeSkills and Gromissingcrafts, hangs entirely on a scratch child frame of `GameTooltip` listening for `OnShow`, where pfUI's ItemID and sell-price lines hook the `Set*` methods themselves and so re-run on every refresh. The reference also clears twice per `SetOwner` (once inside the hide, once in the core) and so fires `OnTooltipCleared` twice; that falls out of calling the same hide, and the existing lifecycle test already allowed for it. Addresses read off the player's own `WoW.exe` with objdump, not from memory. The new test is the hover loop in miniature — three turns of `SetOwner` + a content `Set*` under a `LibItemTooltip`-shaped child probe — and fails on the old behaviour with one `OnShow` instead of three. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn |
||
|
|
1287c328e0 |
creature_anim: the loot sparkle's switch starts where its CVar does
Reported from play: no sparkle on a mining vein, while lootable mobs sparkled fine. That split is the diagnosis — only the GameObject arm reads the switch. `LootSparkleMode` derived `Default`, so it booted `false` while its row registered `"1"`. A boot fires `CvarChanged` only for rows `config.toml` actually carries, and writing `"1"` over a value already `"1"` is a no-op that fires nothing either, so nothing could ever tell the resource its real default — not the boot, and not the player toggling the CVar. The feature was inert for everyone who had never written that row. `vplates.rs`'s `VPlateMode::default` carries this warning in as many words, from `NameplateOcclusion` shipping inert the same way, and this is the same mistake one feature later. So: a hand-written `Default`, and the twin of that module's test, reading the registered row rather than restating it. Swept the rest — of the 32 resources a `CvarChanged` observer writes, 6 derive `Default` and every one of their rows registers `"0"`, so this was the only one. The doc row kept two stale claims and both are corrected: the test has had two arms (the activate bit *or* `GAMEOBJECT_TYPE_CHEST`) since it was widened, and `FxStage` is immaterial here — `Particles\LootFX.m2`, the `"HARDCODED Loot Art"` row's own model, holds one 2400 ms sequence with `flags = 0`, and bit 0 clear loops, so `FxAnimLife::arm` repeats it whatever the stage says. Still unverified in world: whether veins, herbs and chests sparkle, and whether anything sparkles that should not. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn |
||
|
|
aeba81ec2e |
creature_anim: SuperWoW's LootSparkle, the sparkle on lootable treasure
1.12 sparkles a lootable corpse and nothing else — a chest, a herb, a vein or a
quest object is found by eye. Sparkling those arrived in 2.3.0 ("interactable
game objects sparkle and display their names") and SuperWoW backports it as
`LootSparkle`, which upstream describes as "the sparkling effect on lootable
treasure". So on this install the sparkle appears on mobs and not on objects,
which is what was reported.
`arm_loot_fx` grows a GameObject arm beside its unit and corpse ones, hanging the
same "HARDCODED Loot Art" row (0x13) at the same tag, driven by the same field
edges. The three-way decision is a named predicate with a test, because a corpse
falling into the unit arm would read its CORPSE_* bytes as a live unit's, and
because the switch being off has to answer *exactly* 1.12 rather than
approximately it.
The object test has two arms, because the mod's own phrase is "lootable treasure"
and neither arm alone is that:
- `GO_DYNFLAG_LO_ACTIVATE`, the server's per-viewer bit, documented on its own
field reader as "the sparkle". But vmangos raises it through
`GameObject::ActivateToQuest`, so on a chest it means *quest* loot: an ordinary
chest, herb or vein need never carry it, and keying on it alone risks lighting
up nothing but quest objects.
- `GAMEOBJECT_TYPE_CHEST`, which is exactly chests, herbs and mining nodes —
treasure by construction, and a type the client reads without the server's
help. Excluded when `GO_FLAG_IN_USE` or `NO_INTERACT` is set, so an emptied or
disabled chest stops sparkling.
Both arms are a judgement about what the mod does rather than a trace of it —
there is no source and no published default — so they are written to be read and
corrected, and the predicate's own doc says which symptom indicts which arm: only
quest objects lighting up means the first is doing all the work, doors and signs
lighting up means the second is too wide.
Defaulted on: upstream publishes no default, and the sparkle is present on this
install with SuperWoW loaded, which is what "as if its own default config loaded"
means for this compat layer. `SetCVar("LootSparkle", "0")` is stock 1.12.
**Not verified in world.** The harness has no scene, so the test covers the
decision and not the rendering.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
|
||
|
|
0515274afc |
tests: four stale expectations this window's own commits left behind
Four benilla-ui tests were red and had been since the commits that changed what they pin. They were missed because the round's gate was run as `cargo test -p benilla-app -p benilla-ui` through a grep that only showed benilla-app's results; scripts/check.sh is the gate that scopes this correctly. - SetRaidTarget's third argument became a flag rather than the literal word "local" ( |
||
|
|
80648267d5 |
ui_aura: another unit's aura durations, learned from the GO that applies them
The target frame's debuff icons had no countdown. libdebuff was tracking fine — the probe run said `libdebuff guids: 2` after a fight, and the AURA_CAST_ON_OTHER events carried the right lengths (spell 772 Rend, dur 9000) — because the timers pfUI-for-ClassicAPI draws do not come through libdebuff at all. Its unit frames read duration/expirationTime straight off C_UnitAuras.UnitAuraBySlot (api/unitframes.lua:1680,1788), and libdebuff's own header says the plumbing "now runs on ClassicAPI's C_UnitAuras (which provides sourceUnit/sourceGUID and non-player expirationTime)". So the number came from us, and we answered 0 for every unit but the player. There is no packet to answer from: SMSG_UPDATE_AURA_DURATION is self-only, and SMSG_SET_EXTRA_AURA_INFO — which carries a caster's durations on others — is a 2.0 opcode this protocol does not have. Hence every vanilla duration addon keeping its own table. This is that table, kept client-side instead, because this client *is* the client: SMSG_SPELL_GO names the spell and its hit list, so the apply instant is the packet's own arrival rather than a combat-log line somebody has to parse, and every reader of the aura lists gets the timer at once instead of each inventing a tracker. Keyed by (guid, spell) so a re-cast refreshes in place and a recycled slot cannot inherit a stale stamp. Length comes from the same ui_cast_events::aura_duration_ms behind nampower's arg8 and GetSpellDuration, so the three cannot disagree, and its 0 — permanent, no SpellDuration.dbc row, or a combo-point/level-scaled one — stamps nothing: a timerless icon beats one counting to the wrong moment. The join is driven by the unit's own UNIT_FIELD_AURA, so a stamp for an aura the target resisted is never read, and a run-out stamp reports nothing rather than a negative remainder. Not covered, by construction: an aura already on a unit before we streamed it, and one applied out of our range. No GO, no timer — which is what the mods this stands in for also do. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn |
||
|
|
3d4c242c09 |
sound: decode addon .ogg, which is most of the addon audio on this install
unitscan's rare alert was reported as silent. The binding and the loose-file read were both already right: kira was built without the `ogg` container and `vorbis` codec features, so `sfx_from_bytes` returned an error for every Vorbis file and `sound/ui.rs` logged it at `debug!`. A silent alert with nothing in the log to say why. It is not one horn. **75 `.ogg` files ship with the installed addons** — DoiteAuras 57, BigWigs 11, unitscan 3, HCWarn 2, TWThreat and Cleanup one each — so every alert any of them raises by `PlaySoundFile` was dead, HCWarn's hardcore death warnings included. AI_VoiceOver ships `.mp3` and always played, which is exactly what made this look like an addon-specific fault rather than a missing decoder. Stock 1.12 plays wav and mp3 only. The deviation is from 1.12 and toward the client the player actually runs: these are Turtle/OctoWoW addons shipping ogg because that client plays it. `docs/ADDON_COMPAT.md` carries the row. The test decodes a real `.ogg` out of the player's own addon folder and skips when there is none, the same gate the chain-backed tests use for a WoW install — rather than carrying someone else's audio into the tree as a fixture. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn |
||
|
|
0a90795555 |
compat: publish LOCALIZED_CLASS_NAMES_MALE/_FEMALE, the table tInvert exists to flip
Half this pair was already here. `ClassInfo::class_file`'s own doc says the token is what FrameXML keys `RAID_CLASS_COLORS` and `LOCALIZED_CLASS_NAMES_MALE` on, and `compat/classic_api/lua_ext.rs`'s `tInvert` names pfUI's `pfUI.lua:227` as the reason it exists — the line that inverts exactly this table. The table itself was never published, so that line inverted `nil`. Measured against the live chain: `pfUI.env.L["class"]` holds **0 entries**, and `L["class"]["Warrior"]` is nil. Every lookup through it then finds no class and skips, silently, because the consumer tests `if class then` — so the guild roster lost its name colours and class icons (`modules/socialmod.lua:18`), the friends list lost the same, and `modules/panel.lua:359` fell back to white. Chat stayed coloured throughout, which is what made it look like a pfUI setting rather than a missing global. Published from the class table the app already feeds for `C_CreatureInfo.GetClassInfo`, at that same feed, so there is one source for the names and no second reader of the DBC. Both tables carry the same string: 1.12's `ChrClasses.dbc` has one name column, so the gendered pair later clients ship cannot be derived from this data — a copy is what it honestly supports, and omitting `_FEMALE` would hand a caller nil where its sibling answers. A run that never feeds the table leaves both absent rather than empty. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn |
||
|
|
751cac062b |
compat: C_Spell.GetSpellEffectMechanics, the per-effect sibling
`SuperCleveRoidMacros/ClassicAPI.lua:267` calls it unguarded, so its absence was a raise a
session. It is not a second spelling of `GetSpellMechanicByID`, which this tree already answers:
that one reads `Spell.dbc`'s spell-level `Mechanic`, and vanilla stores some mechanics on an
*effect* instead. The consumer's own comment names the case — Rake's bleed is effect-level, so
the spell-level column says 0 while this answers `{0, 15, 0}` — and a macro conditional asking
whether its target is bleeding gets the wrong answer from the spell-level field alone.
`SpellDisplay::effect_mechanic` was already parsed for the cast-immunity scan, so this is a field
on the probe payload and a reader, not a new lookup. The three-way answer is its sibling's, for
the sibling's reason: nothing at all for an id no spell owns, three zeros for a real spell that
carries no mechanic anywhere, the ids otherwise — collapsing those two would reclassify crowd
control as ordinary damage, which is the hand-kept table this function exists to replace.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
|
||
|
|
06011e5e5b |
ui_script: the LoadOnDemand log line stops claiming "not loaded"
A LoadOnDemand addon can already be in memory when the walk reaches its row, because an addon ahead of it loaded it from its own `ADDON_LOADED` — which is how every AI_VoiceOver data module actually arrives: 224 ms of Lua inside AI_VoiceOver's handler, before the walk gets to the `AI_VoiceOverData_*` rows at all. The line was printed unconditionally, so it went on saying "no LoadAddOn() yet" about addons that had loaded. That reads as a bug and is not one; it cost a session chasing a missing `GetAddOnEnableState` that was never the problem. Gated on a new `UiScript::addon_is_loaded`. `docs/ADDON_COMPAT.md` now records why that global stays unbuilt, since the evidence was gathered anyway: upstream ClassicAPI's own `docs/API.md` does not define it either, and AI_VoiceOver gates a modern-shaped `GetAddOnInfo` on the same `if not GetAddOnEnableState`, so defining it would leave `VoiceOver.lua:84` reading `loadable` where it means `reason` and pop the "delete your old folder" dialog on every login. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn |
||
|
|
75f04b4f28 |
compat: FontString:GetStringHeight, which AceGUI calls unguarded
1.12 registers no `GetStringHeight` (`script/button.rs`: `0x782390` reads the label's height instead) and ClassicAPI backports it, so `compat/classic_api/frame.rs` is its home. That file had it on its own not-built list under "needs per-line layout facts the host measurer is not asked for today" — true of `GetNumLines` and `GetWrappedWidth`, and not of this one: the whole-string height is the other half of the number `GetStringWidth` already reads. `measure::current_measure` now owns the staleness rule for both, so the two cannot disagree about when a measure has gone stale, and a changed string still reads 0 rather than the previous string's height. It is not cosmetic. AceGUI-3.0 calls it **unguarded** — `AceGUIWidget-Label.lua:54` is `height = label:GetStringHeight()` and `AceGUIWidget-CheckBox.lua:90` is `self:SetHeight(28 + self.desc:GetStringHeight())` — so every Ace options panel on this install drew its labels and checkbox descriptions at the library's own "avoid zero-height labels" fallback of one pixel: a window with all of its prose invisible. `addon_harness --probe AI_VoiceOver` builds that panel's full 32-widget tree with zero errors and every `Label` at `624x1`, which is what "can't see the actual options in VoiceOver" was. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn |
||
|
|
81d96af35d |
Everwood: the HD/OctoWoW line of benilla, imported onto upstream
This checkout had no git history at all, so this one commit carries the whole
of the Everwood work as a single import on top of chairmanpow/benilla-octo's
`main` (
|