Skip to main content

Network Update

This packet informs the client about which NetObjs have been created, updated, or removed. The server may compress sub-updates by comparing them to the previous update, and only sending the differences.

  • ID: 0x16 (22)
  • Size: Variable
  • LZ4 Compressed: Yes
  • State: Playing
  • Bound To: Server -> Client

Structure

Field NameField TypeNotes
Tickbe u32The tick at which the packet was sent.
Sub Updates(RawUpdate | DeltaUpdate)[Until EOS]The (compressed) sub-updates.

The compression algorithm

In short, the compressed data is a sequence of updates. Each update is either a raw update or a delta update. A RawUpdate is a sequence of bytes that represents the full state of the update. A DeltaUpdate is a sequence of bytes that represents the differences between the previous update and the current update. The first 1-8 bytes of a delta update are a bitfield that indicates which bytes from the previous update should be kept. The remaining bytes are the new bytes that should be added to the previous update.

The first bit of the first byte of an update indicates whether the update is a raw update or a delta. If the bit is 0, the update is a raw update. If the bit is 1, the update is a delta.

RawUpdate

A RawUpdate is a sequence of bytes that represents the full state of the update.

As the first bit of the first byte of an update indicates whether the update is a raw update or a delta, the first byte of a RawUpdate is always 0. This means that the size of the update is always between 0 and 0x7fff bytes.

Field NameField TypeNotes
Sizebe u16The size of the update, including the size field.
Datau8[Size-2]The update data.

DeltaUpdate

A DeltaUpdate is a sequence of bytes that represents the differences between the previous update and the current update. The first bit of the first byte of a DeltaUpdate is always 1. The update, once reconstructed, is the same size as the previous update.

The size is the previous update must be known to be able to reconstruct an update. This size can then be used to determine the size of the bitfield using the formula (size + 8) >> 3, where size is the size of the previous update, excluding the size field. The bitfield is always between 1 and 8 bytes in size.

The bitfield is used to determine which bytes from the previous update should be kept. If a bit is set, the corresponding byte from the previous update should be kept. If a bit is not set, the next byte from the compressed data should be used. As the bitfield can be at most 8 bytes in size, and the first bit of the first byte of a DeltaUpdate is always 1, the size of the update is always between 0 and 0x3f bytes.

Reconstruction pseudo-code

To reconstruct a DeltaUpdate, the previous uncompressed data and the compressed data stream are required. The following pseudo-code can be used to reconstruct a DeltaUpdate:

# Requirements:
# `prevUncompressedData` | The data of the previous update, excluding the size field.
# `stream` | The data stream containing the delta update.

u16ObjSize = len(prevUncompressedData)
assert u16ObjSize <= 0x3f

uBitfieldSizeInBytes = (u16ObjSize + 8) >> 3
assert uBitfieldSizeInBytes > 0 and uBitfieldSizeInBytes <= 8

# Read the bitfield from the compressed data stream.
bitfield = stream.read(uBitfieldSizeInBytes)

# Create a copy of the previous uncompressed data
reconstructedData = list(prevUncompressedData)

# Iterate over the bitfield in reverse order.
for (byteIndex, keep) in enumerate(reversed(bitfield)):
# Read at most `u16ObjSize` bytes from the compressed data stream.
if byteIndex >= u16ObjSize:
break

# If the bit is set, keep the corresponding byte from the previous uncompressed data.
# If the bit is not set, use the next byte from the compressed data and increment the index.
if not keep:
reconstructedData[byteIndex] = stream.read(1)

# Consecutive DeltaUpdates are also relative to the previous DeltaUpdate, so the previous uncompressed data
# must be updated with the reconstructed data.
prevUncompressedData = reconstructedData

return reconstructedData

Example

Consider the following example:

The previous uncompressed data is 00 0a 64 00 00 00 01 00 00 00.
The compressed data is 80 ef 02.

u16ObjSize = len(64 00 00 00 01 00 00 00) = 8
uBitfieldSizeInBytes = (8 + 8) >> 3 = 2
bitfield = 80 ef = 10000000 11101111

  1. Keep 4 bytes from the previous uncompressed data, as we start from the right
    64 00 00 00
  2. Read 1 byte from the compressed data stream.
    64 00 00 00 02
  3. Keep 3 bytes from the previous uncompressed data.
    64 00 00 00 02 00 00 00
  4. u16ObjSize bytes have been read, so stop.

The reconstructed data (without the prefixed size) is 64 00 00 00 02 00 00 00.
Finally, the previous uncompressed data is updated to 00 0a 64 00 00 00 02 00 00 00.

NetworkUpdate

The data of a RawUpdate or decompressed DeltaUpdate can be parsed as a NetworkUpdate structure.

Field NameField TypeNotes
Update Typeenum UpdateType : 3 bitsThe type of the update.
NetObj Typeenum NetObjType : 5 bitsThe type of the NetObj that the update is for.
Controller Typeu8The controller type of the NetObj. Only present for Create updates. For all other updates, this field is not present, and the game does not read anything for this update type.
NetObj IDbe u32The ID of the NetObj that the update is for.
Data...The data of the update. The structure of this field depends on the Update Type and NetObj Type. See the table of updates for more information.

UpdateType Enum

This is an exhaustive list. Updates with values not listed in this table are printed as ERR in the console.

IDNameNotes
1Create
2PWhat P stands for is not yet known. It is sent when a creation that already contains a joint is updated. It can only be sent with a Joint NetObj type.
3Update
5Remove

NetObjType Enum

This is an exhaustive list.

IDNameCreatePUpdateRemove
0RigidBodyCreateRigidBodyUpdateRigidBodyRemoveRigidBody
1ChildShapeCreateChildShapeUpdateChildShapeRemoveChildShape
2JointCreateJointPJointUpdateJointRemoveJoint
3ControllerCreateControllerUpdateControllerRemoveController
4ContainerCreateContainerUpdateContainerRemoveContainer
5HarvestableCreateHarvestableUpdateHarvestableRemoveHarvestable
6CharacterCreateCharacterUpdateCharacterRemoveCharacter
7LiftCreateLiftUpdateLiftRemoveLift
8ToolCreateToolUpdateToolRemoveTool
9PortalCreatePortalUpdatePortalRemovePortal
10PathNodeCreatePathNodeUpdatePathNodeRemovePathNode
11UnitCreateUnitUpdateUnitRemoveUnit
12VoxelTerrainCellCreateVoxelTerrainCellUpdateVoxelTerrainCellRemoveVoxelTerrainCell
13ScriptableObjectCreateScriptableObjectUpdateScriptableObjectRemoveScriptableObject
14ShapeGroupCreateShapeGroupUpdateShapeGroupRemoveShapeGroup

Updates


CreateRigidBody

This update is sent when a RigidBody is created. This can occur when a new RigidBody is added to the world or when a RigidBody is loaded from a save file.

There are two different structures for CreateRigidBody updates. The structure to use is determined by the Controller Type field.

Controller Type 1 - Static

This structure represents a static RigidBody. It is connected to the terrain and has a fixed position and rotation.

Field NameField TypeNotes
World IDbe u16The ID of the world that the RigidBody is in.
RotationQuatWZYXThe rotation of the RigidBody.
PositionVec3fXYZThe position of the RigidBody.
Controller Type 2 - Dynamic

This structure represents a dynamic RigidBody. It is affected by physics and can move.

Field NameField TypeNotes
World IDbe u16The ID of the world that the RigidBody is in.
TransformTransformThe transform of the RigidBody.

UpdateRigidBody

This update is sent when a RigidBody is updated. This can occur when a child shape or joint is added, removed, painted or anything else that affects the RigidBody.

There are two different structures for UpdateRigidBody updates. The structure to use is determined by the Controller Type field. Because the Controller Type field is not present for Update updates, the parser must statefully keep track of the controller type for each RigidBody, or attempt to recover the controller type based on (the size of) the update data.

Controller Type 1 - Static
Field NameField TypeNotes
Unknownu8Unknown. Always 0.
Unknown 2be s32Unknown. Always -1.
Controller Type 2 - Dynamic
Field NameField TypeNotes
Unknownu8Unknown. Always 0.
Revisionu8The revision of the RigidBody. This value increases every time the RigidBody is updated by at least one.

RemoveRigidBody

This update is sent when a RigidBody is removed. This occurs when a RigidBody is deleted from the world. When a static RigidBody is converted to a dynamic RigidBody, the static RigidBody is removed and a dynamic RigidBody is created.

Field NameField TypeNotes
N/AN/ANo additional data is sent.

CreateChildShape

This update is sent when a ChildShape is created.

Field NameField TypeNotes
N/AN/ANo additional data is sent.

The type of the child shape is determined by the Controller Type field.

Controller TypeChild Shape TypeNotes
31BlockThe child shape is a block.
32PartThe child shape is a part. Interactable parts and scripted parts are also of this type.

No other child shape types are known.

UpdateChildShape

This update is sent when a ChildShape is updated. This occurs when the shape is painted. Changing the bounds of a block requires creating a new child shape.

Field NameField TypeNotes
Uuidbe u16The index of the UUID of the child shape in the UUID Network Map.
Body IDbe u32The ID of the RigidBody that the child shape is attached to.
Local PositionVec3i16XYZThe position of the child shape, in coordinates local to the body.
Colorbe u32The color of the child shape. Stored as 0xAABBGGRR.
DataChildShapeDataThe data of the child shape. This field is different for blocks and parts and is determined by the Controller Type field.

ChildShapeData

There are two different structures for ChildShapeData updates. The structure to use is determined by the Controller Type field. Because the Controller Type field is not present for Update updates, the parser must statefully keep track of the controller type for each ChildShape, or attempt to recover the controller type based on (the size of) the update data.

Controller Type 31 - Block
Field NameField TypeNotes
BoundsVec3i16XYZThe bounds of the block.
Controller Type 32 - Part
Field NameField TypeNotes
Z Axis4 bitsThe Z axis of the part.
X Axis4 bitsThe X axis of the part.

To decode an axis, subtract 4 from its value to get a value that represents the axis. The axis values are described in the Local Rotation concept.

RemoveChildShape

This update is sent when a ChildShape is removed.

Field NameField TypeNotes
N/AN/ANo additional data is sent.

CreateJoint

This update is sent when a Joint is created.

Field NameField TypeNotes
N/AN/ANo additional data is sent.

The type of the joint is determined by the Controller Type field.

Controller TypeJoint Type
2Bearing
3Spring
4SurvivalSpring
28Piston and SurvivalPiston
41GenericRotational

PJoint

This update is sent for every Joint attached to a RigidBody when the RigidBody is revised, or when the joint is painted. It is not sent when the first joint of a creation is placed. Bodies on both the A and B side of the joint are able to trigger this update. The reason for this update is not yet known.

Field NameField TypeNotes
N/AN/ANo additional data is sent.

UpdateJoint

This update is sent when a Joint is placed, painted or a PJoint update is triggered.

Field NameField TypeNotes
Uuidbe u16The index of the UUID of the join in the UUID Network Map.
ID Shape Abe u32The ID of the child shape on side A of the joint.
ID Shape Bbe u32The ID of the child shape on side B of the joint.
Pos AVec3i32XYZThe position of the face of the joint on side A, in coordinates local to the body.
Pos BVec3i32XYZThe position of the face of the joint on side B, in coordinates local to the body.
AxisJointAxisThe axis of the joint.
Colorbe u32The color of the joint. Stored as 0xAABBGGRR.

For bearings, Pos A and Pos B are the same, as both faces are at the same position. For pistons, Pos A and Pos B differ by one unit, as the faces are one unit apart.

JointAxis

The orientations of the faces of the joint are represented by pairs of X and Z axes, in coordinates local to the body.

Field NameField TypeNotes
Z Axis B4 bitsThe Z axis of side B.
Z Axis A4 bitsThe Z axis of side A.
X Axis B4 bitsThe X axis of side B.
X Axis A4 bitsThe X axis of side A.

To decode an axis, subtract 4 from its value to get a value that represents the axis. The axis values are described in the Local Rotation concept.

RemoveJoint

This update is sent when a Joint is removed.

Field NameField TypeNotes
N/AN/ANo additional data is sent.

CreateContainer

This update is sent when a Container is created. When joining a world, the client receives a CreateContainer update for their inventory and carry container.

Field NameField TypeNotes
Sizebe u16The amount of slots in the container.
Stack Sizebe u16The maximum stack size of the container.
ItemsContainerItem[Size]The items in the container.
Filter Countbe u16The amount of filters in the container.
Filtersbe u128[Filter Count]The filters of the container.

ContainerItem

Field NameField TypeNotes
Uuidle u128The UUID of the item. Empty slots store the nil UUID 00000000-0000-0000-0000-000000000000.
Tool Instance IDbe u32The instance ID of the item. Always 0xFFFFFFFF for items that are not tools or if the slot is empty.
Quantitybe u16The quantity of the item. Always 0 for empty slots.

UpdateContainer

Field NameField TypeNotes
Slot Change Countbe u16The amount of slot changes.
Slot ChangesSlotChange[Slot Change Count]The changes to the container slots.
Has Filtersbool : 1 bitWhether or not the container has filters.
Filter Countbe u16The amount of filters in the container. This field is only present if Has Filters is True. Note that this field is not aligned to a whole byte.
PaddingPadding : 7 bitsPadding to align the filters to a whole byte. This field is only present if Has Filters is True.
Filter Uuidsbe u128[Filter Count]The UUIDs of the filters. This field is only present if Has Filters is True. The UUIDs are aligned to whole bytes.

SlotChange

Field NameField TypeNotes
Uuidle u128The UUID of the item. Empty slots store the nil UUID 00000000-0000-0000-0000-000000000000.
Tool Instance IDbe u32The instance ID of the item. Always 0xFFFFFFFF for items that are not tools or if the slot is empty.
Quantitybe u16The quantity of the item. Always 0 for empty slots.
Slotbe u16The slot index in the container.

RemoveContainer

This update is sent when a Container is removed, for example when a chest is destroyed.

Field NameField TypeNotes
N/AN/ANo additional data is sent.

CreateCharacter

This update is sent when a Character is created. This can occur when a player joins the world or when a new character is created.

Field NameField TypeNotes
Steam ID 64be u64The Steam ID of the player.
PositionVec3fZYXThe position of the character.
World IDbe u16The ID of the world that the character is in.
Yawbe floatThe yaw of the character.
Pitchbe floatThe pitch of the character.
Character Uuidle u128The UUID of the character. Always 00000000-0000-0000-0000-000000000000 for players.

UpdateCharacter

This update is sent when a Character is updated. This can occur when a player selects a holds a different item, a character gets downed, a character gets painted, and in many other situations. When a character is created, this update is sent with all four update flags set to True.

This update does not contain the transform of the character. The transform is sent in the 24 - Unreliable Update packet.

Field NameField TypeNotes
Update Movement Statesbool : 1 bitWhether or not the movement states of the character are updated.
Update Colorbool : 1 bitWhether or not the color of the character is updated.
Update Selected Itembool : 1 bitWhether or not the selected item of the character is updated.
Update Is Playerbool : 1 bitWhether or not the character is a player.
Movement StatesMovementStatesThe updated movement states of the character. This field is only present if Update Movement States is True.
Colorbe u32The color of the character. Stored as 0xRRGGBBAA. This field is only present if Update Color is True.
Selected ItemSelectedItemThe selected item of the character. This field is only present if Update Selected Item is True.
Is PlayerIsPlayerWhether or not the character is a player. This field is only present if Update Is Player is True.

MovementStates

Field NameField TypeNotes
Is Downedbool : 1 bitWhether or not the character is downed.
Is Swimmingbool : 1 bitWhether or not the character is swimming.
Is Divingbool : 1 bitWhether or not the character is diving.
Unknownbool : 1 bitUnknown. Possibly whether or not the character is aiming or a value from Tool:setMovementSlowDown.
Is Climbingbool : 1 bitWhether or not the character is climbing.
Is Tumblingbool : 1 bitWhether or not the character is tumbling.

SelectedItem

Field NameField TypeNotes
Uuidle u128The UUID of the selected item. If the character does not have a selected item, this field is the nil UUID 00000000-0000-0000-0000-000000000000.
Instance IDbe u32The tool instance ID of the selected item. Always 0xFFFFFFFF for items that are not tools or if the character does not have a selected item.

IsPlayer

Field NameField TypeNotes
Is Playerbool : 1 bitWhether or not the character is a player.
Player or Unit IDbe u32The ID of the player or unit. If the character is a player, this field is the player ID. If the character is not a player, this field is the unit ID.

RemoveCharacter

This update is sent when a Character is removed. This can occur when a player leaves the world or when a unit dies or is removed.

Field NameField TypeNotes
N/AN/ANo additional data is sent.

CreateLift

This update is sent when a Lift is created. This can occur when a player places their lift, spawns a creation on the lift, or puts a creation on the lift.

Field NameField TypeNotes
Steam ID 64be u64The Steam ID of the player that owns the lift.
World IDbe u16The ID of the world that the lift is in.
PositionVec3i32XYZThe position of the lift. This is in block units relative to the origin of the world, not in meters.
Levelbe s32The level of the lift. This is the height of the lift in block units. A value of 0 means that the lift is not extended.

UpdateLift

This update is sent when a Lift is updated. This can occur when a player extends or retracts their lift.

Field NameField TypeNotes
Levelbe s32The level of the lift. This is the height of the lift in block units. A value of 0 means that the lift is not extended.

RemoveLift

This update is sent when a Lift is removed. This can occur when a player removes their lift, a creation is spawned on the lift, or a creation is put on the lift. In the last (two) case(s), the old lift is removed and a new lift is created.

Field NameField TypeNotes
N/AN/ANo additional data is sent.

CreateTool

This update is sent when a Tool is created. All tools that exist in the save file are sent to the client when they join the world. Dragging a tool from the unlimited inventory to the hotbar also creates a new tool.

Field NameField TypeNotes
Uuidle u128The UUID of the tool.

UpdateTool

This update is sent when a Tool is created or updated. It assigns a tool to a player.

Field NameField TypeNotes
Player IDbe u32The ID of the player that the tool is assigned to. When the tool is put into a chest, this field is 0xFFFFFFFF.

RemoveTool

This update is sent when a Tool is removed. This can occur when a player dragges a tool from the hotbar to the unlimited inventory.

Field NameField TypeNotes
N/AN/ANo additional data is sent.

Other structures

Structure definitions that are used in updates.

Transform

This structure represents a transform of a physical object in a world.

Field NameField TypeNotes
RotationQuatXYZWThe rotation of the object.
PositionVec3fXYZThe position of the object.
VelocityVec3fXYZThe velocity of the object.
Angular VelocityVec3fXYZThe angular velocity of the object.

Vec3fXYZ

Field NameField TypeNotes
Xbe floatThe 32-bit, floating point, X coordinate.
Ybe floatThe 32-bit, floating point, Y coordinate.
Zbe floatThe 32-bit, floating point, Z coordinate.

Vec3fZYX

Field NameField TypeNotes
Zbe floatThe 32-bit, floating point, Z coordinate.
Ybe floatThe 32-bit, floating point, Y coordinate.
Xbe floatThe 32-bit, floating point, X coordinate.

Vec3i32XYZ

Field NameField TypeNotes
Xbe s32The signed 32-bit integer X coordinate.
Ybe s32The signed 32-bit integer Y coordinate.
Zbe s32The signed 32-bit integer Z coordinate.

Vec3i16XYZ

Field NameField TypeNotes
Xbe s16The signed 16-bit integer X coordinate.
Ybe s16The signed 16-bit integer Y coordinate.
Zbe s16The signed 16-bit integer Z coordinate.

QuatXYZW

Field NameField TypeNotes
Xbe floatThe 32-bit, floating point, X coordinate.
Ybe floatThe 32-bit, floating point, Y coordinate.
Zbe floatThe 32-bit, floating point, Z coordinate.
Wbe floatThe 32-bit, floating point, real, W coordinate.

QuatWZYX

Field NameField TypeNotes
Wbe floatThe 32-bit, floating point, real, W coordinate.
Zbe floatThe 32-bit, floating point, Z coordinate.
Ybe floatThe 32-bit, floating point, Y coordinate.
Xbe floatThe 32-bit, floating point, X coordinate.