Skip to content

Basic info

Asunaya edited this page Nov 4, 2017 · 1 revision

Command structure and transmission

Basic info commands are MCommands (MC_PEER_BASICINFO in vanilla Gunz and MC_PEER_BASICINFO_RG in RGunz) that contain basic physical information about player characters, which is sampled and transmitted periodically.

Vanilla

In vanilla Gunz, the command is MC_PEER_BASICINFO, which contains:

Pre-serialization type Post-serialization type Description
32-bit float 32-bit float Gametime, in seconds, at the point it was sent
3 x 32-bit float 3 x 16-bit int Position
3 x 32-bit float 3 x 16-bit int Velocity
3 x 32-bit float 3 x 16-bit int Direction
8-bit int 8-bit int Upper animation index
8-bit int 8-bit int Lower animation index
8-bit int 8-bit int Selected item slot

Total size after serialization: 25 bytes.

This is stored as ZBasicInfo before and after serialization, and is serialized into ZPACKEDBASICINFO.

The position and velocity are pairwise converted to shorts (i.e. floored and wrapped into the short range). The direction, a unit vector, is multiplied by 32000 and converted to short.

This command is sent 10 times per second; that is, every 100 milliseconds.

Issues

  • Because shorts can only represent values between -32768 and 32767, position element values that are outside of that will wrap. This means that on very large maps, you can't see other players at the edges.

  • The direction stored is the direction of the character mesh, not the direction the player is pointing at. This means that when the character direction is locked (such as when you're dashing, or hanging with a melee weapon), the actual direction the player is looking into is not recorded, and can't be seen in replays.

    This also means that when the direction gets unlocked again, the direction in replays will rapidly snap between the old locked direction and the new actual direction. However, because of how slowly it's updated by default, this ends up looking smooth (since it is interpolated over 100 milliseconds).

    This becomes problematic when you increase the basic info rate, since it will snap faster and look jittery. E.g., with a 10 ms basic info interval, the 10 ms interpolation period will be imperceptible and it will look like an instant snap.

  • Because only the animation you're doing is transmitted, and not which frame you're in, clients just start the new animation when they receive it at the first frame. Therefore, the animation frame can desync between clients relative to the latency between the event and the basic info transmission.

  • Related to the previous issue, the sampling can sometimes miss intervening animations, which will make it seem as though as the animation never ends. This is well-known in the form of animation locking.

    Consider this example of the continuous animations of someone butterflying rapidly:

    Time Lower animation
    0.00-0.22 ZC_STATE_LOWER_GUARD_CANCEL
    0.22-0.28 ZC_STATE_LOWER_IDLE1
    0.28-0.40 ZC_STATE_LOWER_GUARD_CANCEL

    If it samples at times 0.00, 0.10, 0.20, etc., the sampled basic info data will look like this:

    Time Lower animation
    0.10 ZC_STATE_LOWER_GUARD_CANCEL
    0.20 ZC_STATE_LOWER_GUARD_CANCEL
    0.30 ZC_STATE_LOWER_GUARD_CANCEL
    0.40 ZC_STATE_LOWER_GUARD_CANCEL

    However, if it samples at times 0.05, 0.15, 0.25, etc., they will look like:

    Time Lower animation
    0.15 ZC_STATE_LOWER_GUARD_CANCEL
    0.25 ZC_STATE_LOWER_IDLE1
    0.35 ZC_STATE_LOWER_GUARD_CANCEL
    0.45 ZC_STATE_LOWER_GUARD_CANCEL

    In the first case, it won't sample the intervening idle animation, but it will in the second. This means that animation locks are largely dependent on where the basic info sampling period happens to be.

    When the animation never appears to change, the receiving player never resets the frame count (from the previous point), so it stays stuck on the end of the same animation.

Refined Gunz

In Refined Gunz, the command is MC_PEER_BASICINFO_RG, which contains:

Pre-serialization type Post-serialization type Description Presence
N/A 8-bit int Flag value that indicates which optional fields
are present
Always
32-bit float 32-bit float Gametime, in seconds, at the point it was sent Always
3 x 32-bit float 3 x 16-bit int Short position If 1st flag bit = 0
3 x 32-bit float 3 x 32-bit float Long position If 1st flag bit = 1
3 x 32-bit float 3 x 16-bit int Velocity Always
3 x 32-bit float 2 x 8-bit int Mesh direction Always
3 x 32-bit float 2 x 8-bit int Camera direction If 2nd flag bit = 1
8-bit int 8-bit int Upper animation index If 3rd flag bit = 1
8-bit int 8-bit int Lower animation index If 3rd flag bit = 1
8-bit int 8-bit int Selected item slot If 4th flag bit = 1

Total size after serialization: 21 bytes plus...

  • 6 bytes, if 1st flag bit = 1
  • 2 bytes, if 2nd flag bit = 1
  • 2 bytes, if 3rd flag bit = 1
  • 1 byte, if 4th flag bit = 1

The changes from vanilla are:

  • Some fields are optional, indicated by the flag
  • The position, if out of the range of a short vector, is instead stored in its original format, as a float vector
  • The direction(s) are stored as 2 x 8-bit yaw and pitch, instead of 3 x 16-bit unit vector values
  • The camera direction is stored if the mesh direction is locked
  • Animation indices and selected slot are optional, and only included if new information would be transmitted by their inclusion
  • The basic info rate is now 33 per second (33 ms interval) by default, up from 10 per second (100 ms interval)

Resolution

The changes resolve the previous issues: positions outside of the short vector range can be transmitted, the camera direction is transmitted when distinct (fixing the lack of information and the snapping at high basic info rate). The issues with animations are not entirely resolved, but partially remedied by the higher basic info rate.

History and prediction

Each player in the game has a basic info history, storing the data from basic info commands from at least a second into the past.

The history can be queried for information at a particular time.

  • When the queried time point lies between two stored basic info times, the history interpolates between them and returns the interpolated data.
  • When it lies prior to any stored basic info time, it returns the earliest data stored.
  • When it lies later than any basic info, it returns the current data on the actual player object.

The current data is set by the most recent basic info. Position and velocity are extrapolated further: All player objects are subject to the movement in ZModule_Movable::OnUpdate, which moves forward them according to their velocity, unless the path is obstructed by static geometry or a player.

When a client has not sent a basic info command for over 1 second, the player has the lost connection icon display above their head, and their velocity will be set to zero every frame, making them stay still. (Note that this happens after ZModule_Movable::OnUpdate is called, which means that applied velocity, e.g. via knockback, will take effect for one frame before being removed.) Gravity is not predictively applied, so such a player can stay suspended in the air.

Gameplay

While actually participating in game, unless a basic info was just received on the current frame, every player will always have some amount of extrapolation applied. Since this is just a guess, it can be wrong, causing prediction errors.

For instance, another player can appear to move in one direction because your client guessed this from their most recent velocity, but they will actually teleport a very short distance away and clearly be moving in a different direction. This is particularly common in vanilla Gunz, because the default basic info rate is extremely low, and because dashes allow you to instantly change velocity (correct prediction relies on velocity being stable).

Also, since directions are not extrapolated, in vanilla Gunz, someone spinning will appear to animate at ~10 FPS, making it clear how slow the game updates over the network.

Spectating and watching replays

When spectating players, a small delay is acceptable since there's nothing you need to react to. Therefore, while spectating, Gunz buffers all basic info commands received within a 200 ms delay in order to know what happens in the future.

Similarly, while watching a replay, all future events in the game are already known, so there's no need to guess.

In both cases, knowledge of future information lets the game interpolate. This is necessary for a smooth viewing experience from a player's perspective, because the lack of extrapolation for directions would otherwise make their view unwatchably jittery.