ui+app: the bag walk, the modifier fold, and two shapes the DLL's pool corrected
Built: `GetBagItems`, `GetSpellModifiers`, `GetSpellIdCooldown` — plus
fixes to two verbs that already existed and had the wrong *shape*,
found the same way `GetItemIconTexture`'s wrong argument was: by
comparing what we answer against nampower's own string pool.
`GetBagItems()` hands back every occupied bag slot as
`{ [bagIndex] = { [slot] = row } }`. DoiteAuras asks seven times over
and walks both levels with `pairs`, reading `itemId` and `stackCount` —
it is how that addon answers "do I have this item" without 100
`GetContainerItemInfo` calls per condition evaluation. Empty slots and
empty bags are absent, because a nil value in a Lua table *is* absence
and `pairs` is how both levels are read.
`GetSpellModifiers(spellId, modifierType)` answers
`flat, percent, hasModifier`, where percent is the final multiplier (90
means 90% of the original) — the consumer says so in its own words and
multiplies by `percent / 100`. The 26 modifier types come from the
DLL's usage string. The fold is `spell/mods.rs`' existing reader,
reached through a new `spell_modifier_probe` the lifecycle reinstalls
whenever the wire moves the tables: a VM-side reimplementation of a
four-gate 64-bit fold would be free to drift from the mana cost the
action bar greys a button on. The third return is the point — the
consumer tests it before touching either number, and the duration path
multiplies, so `0, 0` for an unmodified spell would zero every debuff
timer that asks.
`GetSpellIdCooldown(spellId)` answers the twenty-key table, from a new
`Cooldowns::legs`: `info`'s three legs kept apart, same walk and same
arms, so the folded number a swirl draws and the legs an addon reads
cannot describe different timers.
**`GetBagItem`'s row had the wrong key names** — `bag`, `slot`,
`count`, `quality`, `link`, `locked`, this client's own
`GetContainerItemInfo` spellings. nampower's row is contiguous at
`0xa8a68` and is the block `GetEquippedItem` already used, so a caller
reading `.stackCount` got nil and no error. One `bag_slot_table` now
serves both, with three columns absent rather than zeroed: this client
resolves a bag slot's enchants to names at the app boundary and keeps
no random-properties id for a container slot, and a zeroed enchant id
would be a claim.
**`GetItemIdCooldown` returned the wrong kind of value** — the stock
`start, duration, enable` triple. At the version we report it is a
table, and the addon layer settles it: `API.GetItemCooldownInfo` and
`API.GetSpellCooldownInfo` return these calls' results *directly* and
their callers index them. `cd.gcdCategoryRemainingMs` is the primary
path of `API.GetGCDRemainingMs`, which the `[gcd]`/`[nogcd]` macro
conditionals divide by 1000 — against a triple that is
`(0.0).gcdCategoryRemainingMs`, which raises. Latent rather than live
(nothing calls those wrappers today), still wrong.
Two things the schedule gates caught and improved:
- The probe refresher started as an exclusive system, which is
ambiguous against everything in Update it is not ordered against:
+274 pairs over the ceiling. `NonSendMut` is all it needs.
- Its memo started as a bare `Local`, which `session.rs`' own gate
refuses — a memo about what this VM has been told must die with the
VM. As a `VmMemo` it now reinstalls on the first frame after a
`/reload`, which is better than the reasoning I had written for the
`Local`: a fresh VM has no closure at all until something installs
one.
One count the earlier sweep got wrong, corrected in the doc:
`GetSpellPower`'s "7 call sites" are BetterCharacterStats' own method
`BCS:GetSpellPower(school)`, not nampower's global. Nothing in the
corpus calls the global at all. A bare name match is not a call site.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
This commit is contained in:
+51
-3
@@ -578,7 +578,6 @@ because it is in the same family — it would flush our own `WoWCombatLog.txt`,
|
||||
| Name | What is missing |
|
||||
|---|---|
|
||||
| `GetSpellRangeData(rangeIndex)` | A `SpellRange.dbc` **row index**, per the DLL's own usage string — not a spell id, which is how this row read until the usage strings were compared against it. `IsSpellInRange` answers the *verdict* from the live usable map, never the yards, so the table is genuinely absent. One more probe beside `install_spell_extras_probes` |
|
||||
| `GetSpellModifiers(spellId)` | `spell/mods.rs` stores the server's `SPELLMOD_*` cells but publishes no reader |
|
||||
| `GetUnitData(unit)` | The whole descriptor as a table, where `GetUnitField` answers one field. Needs a decision about which fields: the real one returns GUID-typed columns (charm, summon, charmedBy) this client's field reader does not type |
|
||||
| `GetTrinketCooldown()` | Equipped-slot cooldowns. `container_cooldowns` is keyed `(bag, slot)`; the equipped slots are not in it |
|
||||
|
||||
@@ -678,6 +677,55 @@ variable, and `reference_ui.rs`'s beyond-1.12 event gate reads the string litera
|
||||
it had been blind to that pair for as long as they had existed. Both fire sites are literals now,
|
||||
with their rows; verified by probe (renaming either row makes the gate name it).
|
||||
|
||||
### The bag, modifier and cooldown group — and two more shapes the string pool corrected
|
||||
|
||||
**Built: `GetBagItems`, `GetSpellModifiers`, `GetSpellIdCooldown`.** Plus two fixes to verbs that
|
||||
already existed and had the wrong *shape*, both found the same way as `GetItemIconTexture`'s wrong
|
||||
argument — by comparing what we answer against the DLL's own string pool.
|
||||
|
||||
| Verb | What it needed |
|
||||
|---|---|
|
||||
| `GetBagItems()` | every occupied bag slot as `{ [bagIndex] = { [slot] = row } }`. DoiteAuras asks seven times over and walks both levels with `pairs`, reading `itemInfo.itemId` and `itemInfo.stackCount` — it is how that addon answers "do I have this item" without 100 `GetContainerItemInfo` calls per condition evaluation. Empty slots and empty bags are **absent**, because a nil value in a Lua table *is* absence and `pairs` is how both levels are read |
|
||||
| `GetSpellModifiers(spellId, modifierType)` | `flat, percent, hasModifier`, where `percent` is the **final multiplier** (90 means 90% of the original) — the consumer says so in its own words and multiplies by `percent / 100`. The 26 modifier types come from the DLL's usage string. The fold is `spell/mods.rs`' existing reader, reached through a new `Model::spell_modifier_probe` the lifecycle **reinstalls** whenever the wire moves the tables: a VM-side reimplementation of a four-gate 64-bit fold would be free to drift from the mana cost the action bar greys a button on |
|
||||
| `GetSpellIdCooldown(spellId)` | the twenty-key cooldown table, from a new `Cooldowns::legs` — `info`'s three legs kept apart, same walk and same arms, so the folded number a swirl draws and the legs an addon reads cannot describe different timers. Fed beside the folded triple by the spellbook, the pet book and the bag feeds |
|
||||
|
||||
**`GetBagItem`'s row had the wrong key names.** It answered `bag`, `slot`, `count`, `texture`,
|
||||
`quality`, `link`, `locked` — this client's own `GetContainerItemInfo` spellings. nampower's row is
|
||||
`itemId, permanentEnchantId, tempEnchantId, randomPropertiesId, stackCount, duration,
|
||||
spellChargesRemaining, flags, durability, maxDurability, tempEnchantmentTimeLeftMs,
|
||||
tempEnchantmentCharges`, plus `bagIndex`/`slotIndex` — contiguous at `0xa8a68`, and the same block
|
||||
`GetEquippedItem` already used. So a caller reading `.stackCount` got nil and no error. Now one
|
||||
`bag_slot_table` serves `GetBagItem` and `GetBagItems`, with three columns **absent rather than
|
||||
zeroed**: this client resolves a bag slot's enchants to `SpellItemEnchantment` *names* at the app
|
||||
boundary and keeps no random-properties id for a container slot, so `permanentEnchantId`,
|
||||
`tempEnchantId` and `randomPropertiesId` are not there. A zeroed enchant id would be a claim.
|
||||
|
||||
**`GetItemIdCooldown` returned the wrong *kind* of value.** It answered `start, duration, enable` —
|
||||
the stock triple. At the version we report it is a table, and the addon layer is what settles it:
|
||||
`API.GetItemCooldownInfo` and `API.GetSpellCooldownInfo` **return these calls' results directly**
|
||||
(`NampowerAPI.lua:1950`, `:2035`) and their callers index them. `cd.gcdCategoryRemainingMs` is the
|
||||
primary path of `API.GetGCDRemainingMs`, which the `[gcd]` and `[nogcd]` macro conditionals divide
|
||||
by 1000 — against a triple that is `(0.0).gcdCategoryRemainingMs`, which raises. Nothing in the
|
||||
corpus calls those two wrappers today, so the bug was latent rather than live; it was still wrong.
|
||||
|
||||
Two decisions written down at the code:
|
||||
|
||||
- **Every `*RemainingMs` is derived in the binding, from `GetTime()` and the leg's absolute
|
||||
start.** A remainder is the one column that changes every frame, and pushing it would churn the
|
||||
spellbook diff sixty times a second — the feed's own note says the absolute start is what keeps a
|
||||
running cooldown out of that diff.
|
||||
- **The reach is the player's book, pet book and bags, which is narrower than upstream's.** It
|
||||
reads the client's whole `SpellHistory`; this reads the per-slot legs those feeds already carry.
|
||||
For a spell the player has not learnt the answer is the same either way, and the one live
|
||||
consumer asks about a book spell by construction (`API.GetGCDRemainingMs` probes with
|
||||
`GetSpellName(1, BOOKTYPE_SPELL)`).
|
||||
|
||||
**One count the earlier sweep got wrong, worth correcting here**: `GetSpellPower`'s "7 call sites"
|
||||
are `BetterCharacterStats`' own method `BCS:GetSpellPower(school)`, not nampower's global. Nothing
|
||||
in the corpus calls the global at all — the only mention is a comment in
|
||||
`SuperCleveRoidMacros/ClassicAPI.lua:272` saying it reads the same client field. A bare name match
|
||||
is not a call site.
|
||||
|
||||
### The whole registered list, accounted for
|
||||
|
||||
With the registration table read (the section above), the surface is no longer a count of what a
|
||||
@@ -694,10 +742,10 @@ comm -23 /tmp/np /tmp/ours
|
||||
|
||||
| Bucket | Count | Names |
|
||||
|---|---|---|
|
||||
| **Built** | 34 | `CancelPlayerAuraSlot`, `CancelPlayerAuraSpellId`, `CastSpellByName`, `CastSpellByNameNoQueue`, `CastSpellNoQueue`, `FindPlayerItemSlot`, `GetBagItem`, `GetEquippedItem`, `GetEquippedItems`, `GetItemIconTexture`, `GetItemIdCooldown`, `GetItemLevel`, `GetItemStats`, `GetItemStatsField`, `GetNampowerVersion`, `GetPlayerAuraDuration`, `GetSpellDuration`, `GetSpellIconTexture`, `GetSpellIdForName`, `GetSpellNameAndRankForId`, `GetSpellRecField`, `GetSpellSlotTypeIdForName`, `GetTrinkets`, `GetUnitField`, `GetUnitGUID`, `IsAuraHidden`, `IsSpellInRange`, `IsSpellUsable`, `PlayerIsMoving`, `PlayerIsRooted`, `PlayerIsSwimming`, `SetMouseoverUnit`, `UseItemIdOrName` |
|
||||
| **Built** | 37 | `CancelPlayerAuraSlot`, `CancelPlayerAuraSpellId`, `CastSpellByName`, `CastSpellByNameNoQueue`, `CastSpellNoQueue`, `FindPlayerItemSlot`, `GetBagItem`, `GetBagItems`, `GetEquippedItem`, `GetEquippedItems`, `GetItemIconTexture`, `GetItemIdCooldown`, `GetItemLevel`, `GetItemStats`, `GetItemStatsField`, `GetNampowerVersion`, `GetPlayerAuraDuration`, `GetSpellDuration`, `GetSpellIconTexture`, `GetSpellIdCooldown`, `GetSpellIdForName`, `GetSpellModifiers`, `GetSpellNameAndRankForId`, `GetSpellRecField`, `GetSpellSlotTypeIdForName`, `GetTrinkets`, `GetUnitField`, `GetUnitGUID`, `IsAuraHidden`, `IsSpellInRange`, `IsSpellUsable`, `PlayerIsMoving`, `PlayerIsRooted`, `PlayerIsSwimming`, `SetMouseoverUnit`, `UseItemIdOrName` |
|
||||
| **Withheld pending grom's say-so** | 8 | `WriteCustomFile`, `ReadCustomFile`, `CustomFileExists`, `ImportFile`, `ExportFile`, `ExecuteCustomLuaFile`, `StartItemExport` (writes a file of its own), `EncryptPassword` + `EncryptedServerLogin` |
|
||||
| **Not ours to add** | 4 | `QueueSpellByName`, `QueueScript`, `ChannelStopCastingNextTick`, `LearnTalentRank` |
|
||||
| **To build, in corpus-call order** | 16 | `GetSpellPower` (7 call sites outside the API layer), `GetBagItems` (7), `GetSpellModifiers` (6), `DisenchantAll` (5), `GetSpellIdCooldown` (3), then `GetSpellRec`, `GetTrinketCooldown`, `UseTrinket`, `GetUnitData`, `GetSpellRangeData`, `CombatLogFlush`, `GetRaidTargets`, `SetLocalRaidTargetIndex`, `GetQuestLogQuestIds`, `GetQuestDialogQuestId` (0 each — present for completeness, not need) |
|
||||
| **To build, in corpus-call order** | 13 | `DisenchantAll` (5 call sites outside the API layer), then `GetSpellPower`, `GetSpellRec`, `GetTrinketCooldown`, `UseTrinket`, `GetUnitData`, `GetSpellRangeData`, `CombatLogFlush`, `GetRaidTargets`, `SetLocalRaidTargetIndex`, `GetQuestLogQuestIds`, `GetQuestDialogQuestId`, `StartItemExport` (0 each — present for completeness, not need) |
|
||||
|
||||
The two that were at the head of that last bucket are now built — see "The cast feed" above for
|
||||
why they came first and what each decision cost.
|
||||
|
||||
Reference in New Issue
Block a user