app+ui: the queue, the disenchant sequencer — 62/62
QueueSpellByName, QueueScript, ChannelStopCastingNextTick, DisenchantAll. Every one of nampower's 62 registered Lua functions is now defined; the `comm -23` in the doc prints nothing. **Upstream's queue has two halves and one is deliberately not built.** The Lua surface — remember this spell, send it when the client will accept it — is `spell/queue.rs`. The other half intercepts *every ordinary press*, so a keypress during a cast is held rather than refused, and it is governed by NP_QueueCastTimeSpells, NP_QueueInstantSpells, the buffer rows and the quickcast pair. That half is not a binding: it changes how every key in the game feels, its bugs are double-casts and dropped casts, and it is not something to switch on as a side effect of porting three function names. Those twenty CVars stay stored-only, each row saying so; the machinery they would sit on is now here, and turning it on is its own piece of work with its own live test. Two of the twenty-two went live, because they are the explicit queue's own knobs: NP_SpellQueueWindowMs and NP_QueueSpellsOnCooldown. Three slots, because upstream has three and its own event says so: SPELL_QUEUE_EVENT's code is 0/1 on-swing, 2/3 normal, 4/5 non-GCD, so the queue is keyed by what kind of cast a spell is — and the kind comes off its own Spell.dbc row through the same three tests the send path makes. Each pops on its own condition: non-GCD and on-swing as soon as no cast guards (neither uses the global cooldown), normal when the GCD has run out too. Three decisions at the queue, each pinned: - A replacement raises no popped event. A second queue of one kind replaces the first — upstream's own log line is `Replacing queued nonGcd spell X with Y` — and the replaced entry was never sent, so a consumer counting pops against queues would see one too many. - An expiry raises nothing either. `*_POPPED` means "this was sent". - The expiry is measured from the end of the cast in flight, not from the moment of queueing, so queueing early during a long cast is not punished. A lapsed deadline cannot shorten the window. **`DisenchantAll` is specified completely by the DLL's own strings**, which is why it is built this way rather than designed. `0x93087` onward carries the argument note, the precondition, the three quality keywords, every stop reason and the three chat lines — one of them as a Lua chunk that builds the item link itself. "move during cast to cancel" is the abort, and it needed nothing new: moving already cancels a cast here, the cast then fails, and a failed cast stops the run. The one piece of machinery a sequencer usually needs is the client's own, and the player's way out is the one upstream documents. How a step's outcome is read: the item is gone, or it is not. There is no "disenchant succeeded" packet — the item leaves the bag. Each step remembers the guid it cast at and waits for it to vanish with a ten-second deadline. An observable test with no new wire plumbing, right for the same reason the cast bar's own edges are. The queue's drain and the sequencer both live inside `drain_spell_casts`, which that file's own note explains: only one system may hold `CastLadder`, because a second would share its resources with every other cast-send system in an order the ambiguity gate cannot know is safe. `spell/queue.rs` names no VM — the first draft mentioned `UiScript` in one comment and `ui_script_wall.rs` counted it as gameplay code naming the VM, which is the gate working as intended: that module really is gameplay state and not a feed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
This commit is contained in:
+66
-20
@@ -476,7 +476,7 @@ real when something else agrees — an addon calling it, or the mod's own docs.
|
||||
|
||||
What it found that this file did not already have: **38 names in `nampower.dll` that grom's addons
|
||||
call**, on top of the families the nampower section above already tracks. Unported, and listed
|
||||
rather than guessed at — `CancelPlayerAuraSlot`, `CancelPlayerAuraSpellId`, `CombatLogFlush`, `CustomFileExists`, `EncryptPassword`, `EncryptedServerLogin`, `ExecuteCustomLuaFile`, `ExportFile`,
|
||||
rather than guessed at — `CancelPlayerAuraSlot`, `CancelPlayerAuraSpellId`, `ChannelStopCastingNextTick`, `CombatLogFlush`, `CustomFileExists`, `EncryptPassword`, `EncryptedServerLogin`, `ExecuteCustomLuaFile`, `ExportFile`,
|
||||
`CastSpellByNameNoQueue`, `CastSpellNoQueue`, `ChannelStopCastingNextTick`, `CombatLogFlush`,
|
||||
`CustomFileExists`, `EncryptPassword`, `ExecuteCustomLuaFile`, `FindPlayerItemSlot`, `GetBagItem`,
|
||||
`GetCastInfo`, `GetCurrentCastingInfo`, `GetItemIconTexture`, `GetItemIdCooldown`,
|
||||
@@ -620,18 +620,11 @@ migrates by itself. The machinery to make the Remember Password tick-box write a
|
||||
now all here — one line in `login/mod.rs` — and that is his call to make, not a side effect of
|
||||
porting two verbs.
|
||||
|
||||
**Needs a feed this client does not have, with what it would take (2).**
|
||||
|
||||
| Name | What is missing |
|
||||
|---|---|
|
||||
| `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 |
|
||||
|
||||
**Not ours to add (3).** `QueueSpellByName`, `QueueScript` and `ChannelStopCastingNextTick` are the
|
||||
spell queue itself, which this client deliberately does not have (see the nampower rows above:
|
||||
namreeb's always-on cast rule is ported, pepo's queue machinery is not). `LearnTalentRank(page,
|
||||
index, rank)` is left out for a narrower reason: `LearnTalent` here learns the *next* rank, so
|
||||
reaching a named rank means queueing the pair once per missing rank, and whether the server accepts
|
||||
that burst is not something this client can check before sending.
|
||||
**Everything below this line is the reasoning of earlier rounds, kept rather than deleted.** Each
|
||||
section records what was missing *then*, what it would have taken, and — where a later round built
|
||||
it — which of those reasons turned out to be wrong. That is the point of keeping them: three of the
|
||||
"needs a feed" rows were answerable with data the tree already had, and a list of only the final
|
||||
answers would hide that the first reading was the expensive mistake.
|
||||
|
||||
### The cast feed — `GetCurrentCastingInfo`, `GetCastInfo` and `SPELL_CAST_EVENT`
|
||||
|
||||
@@ -874,6 +867,62 @@ Three decisions at that mapping:
|
||||
the player. The other five (`guid`, `posX`, `posY`, `power`, `maxPower`) are filled by the
|
||||
caller, which knows them.
|
||||
|
||||
### The queue, and the one verb that needs it — 62/62
|
||||
|
||||
**Built**, on grom's "62/62": `QueueSpellByName`, `QueueScript`, `ChannelStopCastingNextTick` and
|
||||
`DisenchantAll`.
|
||||
|
||||
**Upstream's queue has two halves, and one of them is deliberately not built.** The Lua surface —
|
||||
"remember this spell and send it when the client will accept it" — is `spell/queue.rs`. The other
|
||||
half intercepts **every ordinary press**, so a keypress during a cast is held rather than refused;
|
||||
it is governed by `NP_QueueCastTimeSpells`, `NP_QueueInstantSpells`, `NP_QueueTargetingSpells`,
|
||||
the buffer rows and the quickcast pair. That half is not a binding: it changes how every key in the
|
||||
game feels, it is the one piece whose bugs are double-casts and dropped casts, and it is not
|
||||
something to switch on as a side effect of porting three function names. **Those twenty CVars stay
|
||||
stored-only**, each row saying so. If grom wants press-interception, it is its own piece of work
|
||||
with its own live test — and the machinery it would sit on is now all here.
|
||||
|
||||
Two of the twenty-two went **live**, because they are the explicit queue's own knobs:
|
||||
`NP_SpellQueueWindowMs` (how long past the in-flight cast's end an entry is held before it gives
|
||||
up) and `NP_QueueSpellsOnCooldown` (whether an entry may wait out the spell's own cooldown).
|
||||
|
||||
**Three slots, because upstream has three and its own event says so.** `SPELL_QUEUE_EVENT`'s first
|
||||
argument is `0`/`1` on-swing queued/popped, `2`/`3` normal, `4`/`5` non-GCD — so the queue is keyed
|
||||
by what *kind* of cast a spell is, and the kind comes off its own `Spell.dbc` row through the same
|
||||
three tests the send path makes. Each slot pops on its own condition: non-GCD and on-swing as soon
|
||||
as no cast guards (neither uses the global cooldown), normal when the GCD has run out too.
|
||||
|
||||
Three decisions at the queue:
|
||||
|
||||
- **A replacement raises no popped event.** A second queue of one kind replaces the first — which
|
||||
is upstream's own behaviour, its log line being `Replacing queued nonGcd spell X with Y` — and the
|
||||
replaced entry was never sent. A consumer counting pops against queues would otherwise see one
|
||||
too many.
|
||||
- **An expiry raises nothing either.** `*_POPPED` means "this spell was sent"; a timed-out entry
|
||||
was not, and upstream has no code for one.
|
||||
- **The expiry is measured from the end of the cast in flight**, not from the moment of queueing,
|
||||
so a player who queues early during a three-second cast is not punished for it. A lapsed
|
||||
deadline cannot shorten the window below a fresh queue's.
|
||||
|
||||
**`DisenchantAll` is specified completely by the DLL's own strings**, which is why it is built the
|
||||
way it is rather than designed here. `0x93087`–`0x932d0` carries the argument note
|
||||
(`includeSoulbound: optional number - pass 1 to include soulbound items (defaults to 0)`), the
|
||||
precondition (`You do not have the Disenchant spell`), the three quality keywords (`greens`,
|
||||
`blues`, `purples`), every stop reason, and the three chat lines — one of them as a Lua chunk that
|
||||
builds the item link with `GetContainerItemLink`.
|
||||
|
||||
**"move during cast to cancel" is the abort, and it needed nothing new.** Moving already cancels a
|
||||
cast here (`inflight.rs`'s `local_self_cancel`), the cast then fails, and a failed cast stops the
|
||||
run — so the one piece of machinery a sequencer usually needs is the client's own, and the player's
|
||||
way out is the one upstream documents.
|
||||
|
||||
**How a step's outcome is read: the item is gone, or it is not.** There is no "disenchant
|
||||
succeeded" packet; what happens is the item leaves the bag. Each step remembers the guid it cast at
|
||||
and waits for it to vanish, with a ten-second deadline. Gone is success and the run continues;
|
||||
still there past the deadline is a failure and the run stops with upstream's own line. An
|
||||
observable test with no new wire plumbing — right for the same reason the cast bar's own edges are,
|
||||
that the thing the player sees is the thing the client keys on.
|
||||
|
||||
### The whole registered list, accounted for
|
||||
|
||||
With the registration table read (the section above), the surface is no longer a count of what a
|
||||
@@ -890,15 +939,12 @@ comm -23 /tmp/np /tmp/ours
|
||||
|
||||
| Bucket | Count | Names |
|
||||
|---|---|---|
|
||||
| **Built** | 58 | `CancelPlayerAuraSlot`, `CancelPlayerAuraSpellId`, `CastSpellByName`, `CastSpellByNameNoQueue`, `CastSpellNoQueue`, `FindPlayerItemSlot`, `GetBagItem`, `GetBagItems`, `GetEquippedItem`, `GetEquippedItems`, `GetItemIconTexture`, `GetItemIdCooldown`, `GetItemLevel`, `GetItemStats`, `GetItemStatsField`, `GetNampowerVersion`, `GetPlayerAuraDuration`, `GetQuestDialogQuestId`, `GetQuestLogQuestIds`, `GetSpellDuration`, `GetSpellIconTexture`, `GetSpellIdCooldown`, `GetSpellIdForName`, `GetSpellModifiers`, `GetSpellNameAndRankForId`, `GetRaidTargets`, `GetSpellPower`, `GetSpellRangeData`, `GetSpellRec`, `GetSpellRecField`, `GetUnitData`, `ImportFile`, `ReadCustomFile`, `StartItemExport`, `WriteCustomFile`, `GetSpellSlotTypeIdForName`, `GetTrinketCooldown`, `GetTrinkets`, `GetUnitField`, `GetUnitGUID`, `IsAuraHidden`, `IsSpellInRange`, `IsSpellUsable`, `PlayerIsMoving`, `PlayerIsRooted`, `PlayerIsSwimming`, `LearnTalentRank`, `SetLocalRaidTargetIndex`, `SetMouseoverUnit`, `UseItemIdOrName`, `UseTrinket` |
|
||||
| **Built** | **62** | `CancelPlayerAuraSlot`, `CancelPlayerAuraSpellId`, `CastSpellByNameNoQueue`, `CastSpellNoQueue`, `ChannelStopCastingNextTick`, `CombatLogFlush`, `CustomFileExists`, `DisenchantAll`, `EncryptPassword`, `EncryptedServerLogin`, `ExecuteCustomLuaFile`, `ExportFile`, `FindPlayerItemSlot`, `GetBagItem`, `GetBagItems`, `GetCastInfo`, `GetCurrentCastingInfo`, `GetEquippedItem`, `GetEquippedItems`, `GetItemIconTexture`, `GetItemIdCooldown`, `GetItemLevel`, `GetItemStats`, `GetItemStatsField`, `GetNampowerVersion`, `GetPlayerAuraDuration`, `GetQuestDialogQuestId`, `GetQuestLogQuestIds`, `GetRaidTargets`, `GetSpellDuration`, `GetSpellIconTexture`, `GetSpellIdCooldown`, `GetSpellIdForName`, `GetSpellModifiers`, `GetSpellNameAndRankForId`, `GetSpellPower`, `GetSpellRangeData`, `GetSpellRec`, `GetSpellRecField`, `GetSpellSlotTypeIdForName`, `GetTrinketCooldown`, `GetTrinkets`, `GetUnitData`, `GetUnitField`, `GetUnitGUID`, `ImportFile`, `IsAuraHidden`, `IsSpellInRange`, `IsSpellUsable`, `LearnTalentRank`, `PlayerIsMoving`, `PlayerIsRooted`, `PlayerIsSwimming`, `QueueScript`, `QueueSpellByName`, `ReadCustomFile`, `SetLocalRaidTargetIndex`, `SetMouseoverUnit`, `StartItemExport`, `UseItemIdOrName`, `UseTrinket`, `WriteCustomFile` |
|
||||
|
||||
| **The queue, and the one verb that needs it** | 4 | `QueueSpellByName`, `QueueScript`, `ChannelStopCastingNextTick`, `DisenchantAll` |
|
||||
|
||||
58 + 4 = 62, and the `comm -23` above is the check: run it and the four it prints are exactly the
|
||||
last row — `DisenchantAll` and the queue trio.
|
||||
|
||||
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.
|
||||
**62 of 62.** The `comm -23` above is the check, and it now prints nothing. What remains unbuilt is
|
||||
not a *name* — it is upstream's press-interception half of the queue, which has no Lua surface of
|
||||
its own and whose twenty CVars say at each row that they are stored-only and why.
|
||||
|
||||
### The event table, accounted for
|
||||
|
||||
|
||||
Reference in New Issue
Block a user