NPCs have no move-speed mask, which blocks NPC running #27

Closed
opened 2026-07-27 22:32:11 +00:00 by sickday · 1 comment
Owner

World.Player.report_speed/2 tells the client how to render a player's movement,
derived from the step the tick actually took. NPCs have no equivalent: nothing in
the NPC info encoder sets a move speed.

This is harmless today because NPCs only ever walk one tile per tick, and the
client's default for a slot is walk. It stops being harmless the first time an
NPC needs to run — combat pursuit is the obvious one — because the NPC info
packet carries distance in its movement bits the same way the player one does,
and speed in a separate mask that is not inferred from it. A two-tile NPC step
with no speed mask is animated at walking pace, the client's render queue for
that NPC falls behind by a tile every tick, and once it saturates it starts
discarding queued tiles.

Same defect class as the player-side one, same symptom: the model drawn several
tiles from where the server has it, sliding between hops.

Worth building alongside NPC running rather than before it, but the mask should
be identified and reserved now so the movement work does not have to be revisited.

`World.Player.report_speed/2` tells the client how to render a player's movement, derived from the step the tick actually took. NPCs have no equivalent: nothing in the NPC info encoder sets a move speed. This is harmless today because NPCs only ever walk one tile per tick, and the client's default for a slot is walk. It stops being harmless the first time an NPC needs to run — combat pursuit is the obvious one — because the NPC info packet carries distance in its movement bits the same way the player one does, and speed in a separate mask that is not inferred from it. A two-tile NPC step with no speed mask is animated at walking pace, the client's render queue for that NPC falls behind by a tile every tick, and once it saturates it starts discarding queued tiles. Same defect class as the player-side one, same symptom: the model drawn several tiles from where the server has it, sliding between hops. Worth building alongside NPC running rather than before it, but the mask should be identified and reserved now so the movement work does not have to be revisited.
Author
Owner

Closing — the premise does not hold for rev 149. There is no NPC move-speed mask to identify or reserve, because NPC render speed is not carried by the mask block at all.

method708 reads the movement type and hands it straight to NPC.method1663(direction, speed), which stores it on the queued tile itself: 1 for the single 3-bit step of type 1, and 2 for each of the two steps of type 2. The shared Actor render loop then reads pathTraversed[pathLength - 1] and doubles its per-cycle advance when it is 2 — the same doubling the player path reaches through Players.field1487, off a different source.

So type 2 is the run mask. {:run, first, second} is already the whole of what an NPC needs to run correctly, and the encoder emits it today. The mask block has nothing spare to reserve either: all seven of its bits are taken by other fields and the eighth, 0x80, is never tested in method784.

The player-side defect this was modelled on is real; the two paths just carry speed differently, and only the player one needs a mask. Combat pursuit needs a server that gives an NPC two steps in a tick, and nothing in Network.Game.NpcInfo.

Written up in the NpcInfo and NpcInfo.Mask moduledocs (99a7c76) so the next person to look does not re-derive it.

Closing — the premise does not hold for rev 149. There is no NPC move-speed mask to identify or reserve, because NPC render speed is not carried by the mask block at all. `method708` reads the movement type and hands it straight to `NPC.method1663(direction, speed)`, which stores it on the queued tile itself: `1` for the single 3-bit step of type `1`, and `2` for **each** of the two steps of type `2`. The shared `Actor` render loop then reads `pathTraversed[pathLength - 1]` and doubles its per-cycle advance when it is `2` — the same doubling the player path reaches through `Players.field1487`, off a different source. So type `2` *is* the run mask. `{:run, first, second}` is already the whole of what an NPC needs to run correctly, and the encoder emits it today. The mask block has nothing spare to reserve either: all seven of its bits are taken by other fields and the eighth, `0x80`, is never tested in `method784`. The player-side defect this was modelled on is real; the two paths just carry speed differently, and only the player one needs a mask. Combat pursuit needs a server that gives an NPC two steps in a tick, and nothing in `Network.Game.NpcInfo`. Written up in the `NpcInfo` and `NpcInfo.Mask` moduledocs (99a7c76) so the next person to look does not re-derive it.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Revenant/Server#27
No description provided.