Sauerbraten uses this type of packet to communicate client positions between client(s) and server. A client sends its position to the server every 33 milliseconds, which corresponds to 30 “frames” per second. The server replays packets of clients to all other clients at the same rate.

Schema

Here’s what the N_POS packet looks like (byte by byte):

N_POS
CN
state
flags
x.1 x.2 [x.3] y.1 y.2 [y.3] z.1 z.2 [z.3]
dir.1 dir.2 roll
vel.1 [vel.2] veldir.1 veldir.2
[fall.1 [fall.2] [falldir.1 falldir.2]]

N_POS

This just indicates to the receiver that this packet is a position packet.

CN

Client number of the player whose position is described in the packet (can be > 128 to describe a bot’s position).

state

Describes the parts of the player’s state that are relevant to animating the player model; the bits from low to high are:

  • 3 bits “physical” state (falling, sliding down a slope, moving/standing on floor, etc…)
  • 1 bit life sequence (changes at every respawn)
  • 2 bits move (0b01 = forward, 0b11 = backward, 0b00 = none)
  • 2 bits strafe (0b01 = left, 0b11 = right, 0b00 = none)

flags

Flags that indicate what optional packets to expect; the bits from low to high are:

  • 3 bits indicating the presence x.3, y.3, z.3 respectively
  • 1 bit indicating whether there is a vel.2 byte
  • 3 bits describing the player’s fall:
    • first bit indicates presence of the entire [fall.1 [fall.2] falldir.1 falldir.2] block
    • second bit indicates whether fall.2 is present
    • third bit is set when the two falldir bytes are present: when they are not sent in the position packet, it is assumed that the fall vector is pointing straight down (this way “normal” falling down saves the falldir bytes)
  • 1 bit indicating whether the player is standing in game clip material

x.1 through z.3

Describe the player’s position in the world (third bytes are only present when the values exceed 2 bytes, see flags).

dir.1 dir.2

Contain (compressed) yaw and pitch of the player’s view/“camera” (i.e. the direction the player is looking in):

  • all 16 bits (dir.1 | dir.2 << 8) make up an integer value dir
  • dir mod 360 is the yaw value in degrees
  • (dir / 360) - 90 is the pitch value in degrees (-90° to 90°; -90° ~ looking straight down; 90° ~ looking straight up)

roll

The player’s roll value (with +90 offset like pitch: a 0x00 byte (= 0) means -90° roll, and 0x5A (= 90) means the view is horizontal / 0° roll).

vel.1 [vel.2]

Magnitude of the velocity vector, i.e. the speed the player is moving at.

veldir.1 veldir.2

Yaw and pitch of the velocity vector (compressed like dir.1 dir.2).

fall.1 [fall.2]

Magnitude of the fall vector, i.e. the speed the player is falling at.

falldir.1 falldir.2

Yaw and pitch of the fall vector (compressed like dir.1 dir.2); only sent when it’s not pointing straight down (x ≠ 0 or y ≠ 0 or z > 0).

Example

Here’s an example of a position packet (bytes printed as their decimal representations):

4 1 17 16 11 39 102 35 207 27 134 112 90 132 151 126 35

4         → N_POS
1         → CN
17        → state (17 = 0b00010001 ~ player moves forward, physical
            state: falling)
16        → flags (16 = 0b00010000 ~ fall vector is included in the
            packet)

11 39     → x (9995 ~ 624.6875)
102 35    → y (9062 ~ 566.375)
207 27    → z (7119 ~ 444.9375)

134 112   → direction of camera vector (28806 ~ 6° yaw, -9.984° pitch)
90        → roll of camera (0°)

132       → magnitude of the velocity vector
151 126   → direction of velocity vector (32407 ~ 7° yaw, 0.019° pitch)

35        → magnitude of the fall vector

So in this case, the player is falling (in the air) and at the same time moving forwards. The 0.019° velocity pitch indicates an almost horizontal pitch, possibly shortly before reaching the peak of a jump.