app: a held press cannot time out while something still guards it

The queue stamped each entry's deadline when the press was made. That reads
right and has a hole in it: a cast pushed back twice (`SMSG_SPELL_DELAYED`)
ends after the window a press made at its start was given, so the entry
times out while the thing it is waiting for is still running — and the press
vanishes with no packet, no error line and no cast. That is the one failure
mode a player cannot describe, and it is exactly the class of bug the press
queue was supposed to remove rather than introduce.

The same hole swallows a press held through a cast whose global cooldown
then outlasts the window, which is the ordinary case rather than an edge
one: a 500 ms window, a cast that ends, and 1.5 s of global cooldown behind
it.

So a slot now keeps the window and the base it is measured from, and the
drain ratchets that base forward — only ever forward — past every live
guard: the cast's reopening, the channel's trimmed end, and the global
cooldown's. An entry gives up when nothing is holding it back any more and
the window has run out since, which is what "how long past the guard's end"
meant all along.

Scripts keep a fixed deadline. Their contract is "after queued spells", and
a script whose spells never fired has nothing left to run after.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
This commit is contained in:
2026-10-25 00:30:46 -06:00
co-authored by Claude Opus 5
parent 39932b12da
commit fcdf14c0dc
3 changed files with 121 additions and 29 deletions
+8
View File
@@ -1040,6 +1040,14 @@ a narrower set is the safe direction to be wrong in.
| `NP_OnSwingBufferCooldownMs` | stored | A rate limit on re-queueing an on-swing strike. The slot is live; what is missing is anything to rate-limit, since a re-press of the queued strike already takes the reference's own silent bail (`6e4d43`) and so cannot queue twice |
| `NP_ChannelLatencyReductionPercentage` | **LIVE** | How much of the measured round trip comes off a channel's named end before the queue stops treating it as a guard, so a press held through a channel is not sent a round trip late. The arithmetic is the cast buffer's again, and worth writing down because "send early" sounds reckless: the server starts the channel at `T` and ends it at `T + duration`, we learn of it half a trip later, so our own end is already half a trip *after* the server is free — trimming 75 % of `L` puts a packet at the server a quarter of a trip after its own end. The named end, not the slack-inflated one, for the same reason. With no pong measured yet nothing is trimmed |
**A held entry's window is measured from the guard's *live* end, not the one stamped when the
press was made.** A fixed deadline reads right and has a hole in it: a cast pushed back twice
(`SMSG_SPELL_DELAYED`) ends after the window a press made at its start was given, so the entry
would time out while the thing it is waiting for is still running — and the press would vanish with
no packet and no error line, which is the one failure mode a player cannot describe. The same hole
swallows a press held through a cast whose global cooldown then outlasts the window. So the base
ratchets forward while anything still guards, and only ever forward.
**One behaviour worth knowing before testing.** Pressing a spell during a long cast holds it for the
whole cast and fires it at the end — the last press wins, so pressing three different spells during
a Pyroblast fires the third. That is upstream's `Replacing queued nonGcd spell X with Y` applied to