FigNet Porting Guide
This guide documents the current FigNet + Entangle protocol as implemented in the active C# runtime.
It is meant for developers building:
- a non-C# client
- a new provider
- a protocol debugger or packet inspector
Scope
This guide is based on the active runtime projects only:
FigNetENetProviderLiteNetLibProviderWebSocketCoreProviderEntangleCommonEntangleEntangleServer
This guide intentionally ignores legacy or non-canonical folders for protocol decisions:
BenchmarkingCppClientDemoClientDemoServerentangle-admin-dashboardEntangleCppFigNet_AssetStoreTemplateFigNet_UnitTestFigNet_UnityPackage- Unity
.metafiles
If code and older docs disagree, trust the C# files above.
1. Protocol Layers
There are two protocol layers in the current implementation:
- FigNet core envelope This is the 3-byte header, optional 2-byte magic prefix, callback flag packing, and optional XOR payload encryption.
- Payload codec
Core built-in messages use the legacy
BitBuffercodec. Entangle messages useMemoryPack.
Source of truth:
FigNet/Core/MessageHeader.csFigNet/Core/Message.csFigNet/Core/Internals.csFigNet/Messages/Operations/RegisterIdentity.csFigNet/Messages/Operations/Ping.csEntangleCommon/Serializer/MemoryPack_Serializer.csEntangleCommon/Data/Model/EntangleModels.cs
2. Wire Envelope
All numeric fields in the FigNet envelope are little-endian.
2.1 Without EnableCheckSum
Despite the config name, no checksum is added. The frame layout is:
0..1 msg_id ushort
2 flags byte
3..N payload
2.2 With EnableCheckSum
In the active providers, EnableCheckSum means "prepend a 2-byte magic number".
0..1 magic ushort = 0xC0DE
2..3 msg_id ushort
4 flags byte
5..N payload
Important:
Internals.MAGIC_NUMBERis0xC0DEMessageHeader.HASH_SIZEis2- the name
HASH_SIZEis legacy naming; it is a magic prefix size, not a hash - active providers validate the magic number only
Source of truth:
FigNet/Core/MessageHeader.csFigNet/Core/Internals.csENetProvider/ClientSocketENet.csENetProvider/ServerSocketEnet.csLiteNetLibProvider/ClientSocketLiteNetLib.csLiteNetLibProvider/ServerSocketLiteNetLib.csWebSocketCoreProvider/ClientSocketWS.csWebSocketCoreProvider/ServerSocketWS.cs
2.3 Flags byte
bit 7 IsEncrypted
bits 0..6 callbackId
Rules:
callbackId == 0means fire-and-forgetcallbackIdis limited to1..127- client sockets allocate callback IDs automatically for
SendMessage(message, response) - server handlers usually echo the incoming
message.callbackId
3. Encryption
Encrypted datagram payloads use an app-layer secure session (ECDH on P-256 + HKDF-SHA256 + ChaCha20-Poly1305
AEAD), not XOR. FigNet/Core/XorEncryption.cs is legacy/unused code - it is not referenced by any active provider
(ENetProvider, LiteNetLibProvider, WebSocketCoreProvider) and must not be implemented for compatibility.
For the full handshake, wire format, and a porting checklist for this layer, see Secure Session.
Source of truth:
FigNet/Core/SecureSessionContext.csFigNet/Core/SecureSessionProtocol.csENetProvider/ClientSocketENet.csLiteNetLibProvider/ClientSocketLiteNetLib.cs
4. Transport and Delivery Behavior
The envelope is transport-agnostic. The active providers are:
WebSocketsENetLiteNetLib
4.0 WebSocket-specific connection details
For the current WebSocket provider:
- client connects to
/FigNet - client requests subprotocol
FigNet - server advertises supported subprotocol
FigNet - TLS is optional and controlled by
IsSecure
Source of truth:
WebSocketCoreProvider/ClientSocketWS.csWebSocketCoreProvider/ServerSocketWS.cs
4.1 Delivery methods
FigNet exposes:
UnreliableReliableReliableUnorderedSequenced
Provider semantics are not perfectly identical, so ports should match behavior, not just enum names.
Source of truth:
FigNet/Core/DeliveryMode.csENetProvider/ClientSocketENet.csLiteNetLibProvider/ClientSocketLiteNetLib.csWebSocketCoreProvider/ClientSocketWS.cs
4.2 Entangle channel conventions
Entangle uses these fixed channel IDs:
| Channel | Constant | Typical use |
|---|---|---|
| 0 | RELAIBLE_DATA_CHANNEL | control and reliable room data |
| 1 | ENTITY_STATE_CHANNEL | entity state deltas |
| 2 | ROOM_EVENT_CHANNEL | room events and cached-event traffic |
| 3 | UPDATE_CHANNEL | transform/entity update stream |
Notes:
- the constant name
RELAIBLE_DATA_CHANNELcontains a typo in code and should be preserved when referencing current implementation - room/entity replication relies on both channel ID and delivery mode
Source of truth:
EntangleCommon/Misc/FNConstants.csEntangle/EN.csEntangle/NetworkEntity.csEntangle/Room.csEntangleServer/Modules/Lobby/Components/RoomStateManager.csEntangleServer/Modules/Lobby/AreaOfInterest/AOIGroup.cs
5. Current Handshake and Session Flow
The current runtime does not use the newer conceptual WSS/auth ticket flow described in older docs. The live flow is:
- Connect transport.
- Client auto-sends
60000 RegisterIdentity. - Server validates
AppName,AppSecretKey, andPeerType. - Server replies with the same message ID and same callback ID.
- Entangle client sends
60304 AppKey. - Server replies with
IsSuccess. - Lobby operations start: room list, create room, join room, room traffic.
Important security note:
RegisterIdentityincludes the app secret in the payload- it is not automatically marked
IsEncryptedin the activeFigNet/Messages/Operations/RegisterIdentity.cs - if you run over plain ENet/LiteNetLib or plain
ws, that secret is not transport-protected
Server-side timeout note:
- newly connected peers are disconnected if they fail to register within about 5 seconds
Source of truth:
FigNet/Messages/Operations/RegisterIdentity.csFigNet/Messages/Handlers/RegisterIdentityHandler.csFigNet/Core/Internals.csEntangle/EN.csEntangleCommon/Data/Operation/RegisterAppIdOperation.csEntangleServer/Modules/Lobby/Handlers/OnAppKeyHandler.cs
6. Core Message IDs
| ID | Name | Direction | Payload | Notes |
|---|---|---|---|---|
| 60000 | RegisterIdentity | client -> server, response server -> client | RegisterIdentityModel | current bootstrap message |
| 60001 | ValidateIdentity | reserved legacy | none / incomplete | client handler exists, active server path does not use it |
| 60002 | Ping | request/response | PingModel | echoes peer type and callback ID |
6.1 RegisterIdentityModel
Legacy BitBuffer layout:
| Field | Type | Notes |
|---|---|---|
peerType | byte | 0=Server, 1=Client, 2=Unknown |
SecretKey | ASCII string | short length prefix, then bytes |
AppName | ASCII string | short length prefix, then bytes |
Failure response behavior:
- server responds on the same message ID
SecretKeybecomes"invalid"AppNamecontains the error text
Source of truth:
FigNet/Messages/Operations/RegisterIdentity.csFigNet/Messages/Handlers/RegisterIdentityHandler.csFigNet/Core/BitBuffer.csFigNet/Core/PeerStatus.cs
6.2 PingModel
Legacy BitBuffer layout:
| Field | Type |
|---|---|
peerType | byte |
Source of truth:
FigNet/Messages/Operations/Ping.csFigNet/Messages/Handlers/PingHandler.cs
7. Entangle Message ID Registry
EntangleCommon/OperationCode.cs is the canonical ID registry for the active Entangle stack.
7.1 Lobby and control
| ID | Name | Typical direction | Request payload | Response/Event payload |
|---|---|---|---|---|
| 60300 | CreateRoom | client <-> server | CreateRoomData | CreateRoomResponseData |
| 60301 | JoinRoom | client <-> server | RoomJoinOperationData | RoomJoinOperationData |
| 60302 | GetRoomList | client <-> server | GetRoomListOperationData | RoomListData |
| 60303 | LeaveRoom | client <-> server | LeaveRoomOperationData | LeaveRoomOperationData |
| 60304 | AppKey | client <-> server | RegisterAppIdOperationData | RegisterAppIdOperationData |
| 60305 | ReJoinRoom | client <-> server | RoomReJoinOperationData | RoomReJoinOperationData |
| 60306 | ForcedSyncRoomState | client -> server | NullOperationData | room state replay sequence |
7.2 Room membership and replication
| ID | Name | Typical direction | Request payload | Response/Event payload |
|---|---|---|---|---|
| 60400 | OnPlayerJoinRoom | server -> client | n/a | OnPlayerJoinRoomOperationData |
| 60401 | OnPlayerLeftRoom | server -> client | n/a | OnPlayerLeaveRoomOperationData |
| 60402 | OnEntityState | client -> server, server -> client | EntityStateChangeOperationData | EntityStateChangeResponseOperationData |
| 60403 | OnMasterClientChange | server -> client | n/a | MasterClientChangeOperationData |
| 60404 | InstantiateEntity | client -> server, server -> client | InstantiateOperationData | InstantiateOperationResponseData |
| 60405 | DeleteEntity | client -> server, server -> client | DeleteEntityData | DeleteEntityData |
| 60406 | RoomEvent | client -> server, server -> client | RoomEventData | RoomEventResponseData |
| 60407 | RequestOwnerShip | client -> server, server -> client | RequestOwnershipOperationData | RequestOwnershipOperationData |
| 60408 | ClearOwnerShip | client -> server, server -> client | ClearOwnershipData | ClearOwnershipData |
| 60409 | OnAgentOwnerShipChange | server -> client | n/a | AgentOwnershipChangeOperationData |
| 60410 | RoomStateChange | server -> client | n/a | RoomStateChangeOperationData |
| 60411 | PreRoomStateReceived | server -> client | n/a | NullOperationData |
| 60412 | PostRoomStateReceived | server -> client | n/a | NullOperationData |
| 60413 | EntityUpdate | client -> server, server -> client | EntityUpdateOperationData | EntityUpdateResponseOperationData |
| 60414 | PresenterMode | client -> server, response server -> client | PresenterModeData | PresenterModeData |
| 60415 | OnCachedEventRemoved | server -> client | n/a | RoomEventData |
| 60416 | CustomGroupOperation | client -> server, server -> client | CustomGroupOperationData | CustomGroupOperationData |
| 60417 | Heartbeat | client -> server | NullOperationData | none |
| 60418 | UpdateSeat | client -> server | UpdateSeatOperationData | room-property/state propagation |
7.3 Game extension
| ID | Name | Typical direction | Payload |
|---|---|---|---|
| 60500 | GameMessage | client <-> server/module | GameMessage |
| 60501 | GameState | client <-> server/module | GameState |
7.4 Infrastructure and server-side IDs
These exist in the registry but are usually not needed by a normal gameplay client port.
| ID range | Usage |
|---|---|
| 60050-60055 | load balancer / room registry |
| 62000-62002 | admin panel |
Source of truth:
EntangleCommon/OperationCode.csEntangleAdminPanel/OperationCode.cs
8. Key Payload Shapes
Most Entangle payloads are declared in one file:
EntangleCommon/Data/Model/EntangleModels.cs
8.1 CreateRoomData
| Field | Type |
|---|---|
RoomName | string |
MaxPlayers | ushort |
Password | string |
CellSize | ushort |
Width | ushort |
Height | ushort |
AoiActivationCount | byte |
LodLevel | byte |
ForcedRoomStateSyncInterval | short |
PresenterInGroupCast | bool |
FetchCachedEventsDelay | ushort |
8.2 CreateRoomResponseData
| Field | Type |
|---|---|
RoomId | uint |
RoomName | string |
ResponseCode | RoomResponseCode |
8.3 RoomJoinOperationData
Request fields:
| Field | Type |
|---|---|
RoomId | uint |
TeamId | uint |
Password | string |
UserName | string |
Response fields used by the current client:
| Field | Meaning in response |
|---|---|
ResponseCode | join result |
RoomId | current player ID, not room ID |
RoomAuthToken | room rejoin token |
RoomName | room display name |
8.4 RoomReJoinOperationData
| Field | Type |
|---|---|
RoomId | uint |
TeamId | uint |
Password | string |
IsRejoin | bool |
RoomAuthToken | string |
PeerId | uint |
ResponseCode | RoomResponseCode |
RoomName | string |
8.5 RoomEventData
| Field | Meaning |
|---|---|
RoomId | room ID on request, sender peer ID on server->client event messages |
EventId | user-defined event code |
DeliveryMethod | DeliveryMethod encoded as byte |
Tag | arbitrary grouping/removal tag |
IsCached | cache in room for late joiners |
UniqueId | room-assigned ID for cached-event removal |
Data | raw event payload bytes |
8.6 InstantiateOperationData
| Field | Type |
|---|---|
RoomId | uint |
EntityId | short |
EntityType | EntityType |
TransLite | TransformLite |
8.7 InstantiateOperationResponseData
| Field | Type |
|---|---|
RoomId | uint |
OwnerId | uint |
NetworkId | uint |
EntityId | short |
EntityType | EntityType |
InitialState | EntityDefaultStateData |
8.8 EntityUpdateOperationData
| Field | Type |
|---|---|
RoomId | uint |
EntityNetId | uint |
EntityType | EntityType |
TransLite | TransformLite |
EntityUpdateResponseOperationData is just a batch wrapper:
| Field | Type |
|---|---|
EntitiesUpdate | List<EntityUpdateOperationData> |
8.9 EntityStateChangeOperationData
| Field | Type |
|---|---|
RoomId | uint |
NetworkId | uint |
EntityType | EntityType |
State | EntangleEntityState |
EntityStateChangeResponseOperationData is a batch wrapper:
| Field | Type |
|---|---|
StatesBatch | List<EntityStateChangeOperationData> |
8.10 RequestOwnershipOperationData
Request meaning:
| Field | Meaning |
|---|---|
RoomId | room ID |
IsLocked | lock requested ownership |
EntityNetId | target entity |
EntityType | target type |
Broadcast response meaning:
| Field | Meaning |
|---|---|
RoomId | owner ID, not room ID |
EntityNetId | target entity |
IsLocked | ownership lock state |
EntityType | target type |
8.11 ClearOwnershipData
Request meaning:
| Field | Meaning |
|---|---|
RoomId | room ID |
EntityNetId | target entity |
Broadcast response meaning:
| Field | Meaning |
|---|---|
RoomId | owner ID of the clearer, not room ID |
EntityNetId | target entity |
9. Transform and State Internals
9.1 TransformLite
TransformLite is a MemoryPack object containing:
TransformData : byte[]ConfigFlags : ushortSequenceNumber : ushort
Wire-breaking change (Advanced NetworkTransform):
ConfigFlagswas widenedbyte->ushort(MemoryPack now writes 2 bytes for it). Client and server must be built/deployed from the same commit - an old runtime mis-parses everyTransformLite. Bits 0-7 are unchanged, so existing serialized flag values round-trip; only the field width and the new high bits are new.
TransformData is a custom packed little-endian blob.
ConfigFlags bits:
| Bit | Meaning |
|---|---|
| 0 | include Z position |
| 1 | include rotation |
| 2 | include scale X |
| 3 | include scale Y |
| 4 | include scale Z |
| 5 | half precision scale 1000 |
| 6 | half precision scale 100 |
| 7 | half precision scale 10 |
| 8 | include linear velocity |
| 9 | include angular velocity |
| 10 | velocity half precision (short scale 100, else 1000) |
| 11-15 | reserved |
Rules:
- X and Y position are always present
- Z is optional
- rotation is four
shortvalues and always uses scale1000 - scale axes are optional
- if no precision flag is set, position/scale values are written as 32-bit floats
- if a precision flag is set, position/scale values are written as scaled
short - linear/angular velocity are each three
shortvalues, always short-scaled (never float), and are appended after scale - so position/rotation/scale offsets are unchanged whether or not velocity is present (the server readsPositionfrom the front regardless) - velocity scale is
1000(range +/-32.7, 1 mm/s) unless bit 10 is set, then100(range +/-327, 1 cm/s)
Source of truth:
EntangleCommon/Data/Model/EntangleModels.cs(server) - mirrored inFigNetUnity/.../Entangle/FigNetCommon/Data/Model/EntangleModels.cs(client)
9.2 EntangleEntityState
Entity state replication is a batch of property deltas:
| Field | Type |
|---|---|
DeliveryMethod | byte |
Delta | List<NetworkedObjectBinary> |
SequenceNumber | ushort |
Each NetworkedObjectBinary contains:
| Field | Type |
|---|---|
Key | byte |
Data | byte[] |
Meaning:
Keyis the[Networked(...)]property IDDatais aMemoryPackserialization of the concreteNetworkSerializablepayload
Important porting note:
- server currently registers a dynamic union only for
RoomSeatsData - custom
NetworkSerializablesubtypes must be registered consistently on every runtime
Source of truth:
EntangleCommon/Data/Model/EntangleEntityState.csEntangleCommon/Data/NetworkedObjectBinary.csEntangleCommon/Data/NetworkSerializable.csEntangleCommon/Data/Attributes.csEntangleServer/Modules/Lobby/FNELobby.cs
10. Compatibility Quirks You Must Preserve
These are easy to miss and matter for client ports:
EnableCheckSumadds a 2-byte magic number, not a checksum hash.RoomJoinOperationData.RoomIdis reused as the joining player's ID in the join response.RoomEventData.RoomIdis reused as sender ID in server->client event traffic.RequestOwnershipOperationData.RoomIdis reused as owner ID in the broadcast response.ClearOwnershipData.RoomIdis reused as owner ID in the broadcast response.RegisterIdentityandPinguse the legacyBitBuffercodec, notMemoryPack.- Most Entangle messages use
MemoryPack, so ports need eitherMemoryPackcompatibility or a controlled replacement. ValidateIdentity(60001) is reserved but not part of the active built-in handshake.- Server-side peer IDs are generated by FigNet and should be treated as opaque
uintvalues.
11. Porting Checklist
- Implement the exact FigNet envelope, including the optional
0xC0DEprefix. - Treat all header integers as little-endian.
- Pack callback ID in the low 7 bits and encryption in the high bit.
- If you need
IsEncryptedcompatibility, implement the secure-session handshake (see Secure Session), not XOR - XOR is legacy and unused on the active path. - Implement
60000 RegisterIdentitywith the legacyBitBuffercodec. - Ignore
60001 ValidateIdentityunless you are intentionally reproducing legacy behavior. - Implement
60304 AppKeyafter successful identity registration if you want Entangle compatibility. - Preserve the response field quirks documented above.
- Support
MemoryPackpayloads for Entangle operation and model classes. - Preserve Entangle channels
0/1/2/3and their current use.
12. Recommended Reading Order In This Repo
If a port author wants to trace behavior from top to bottom, read these in order:
FigNet/Core/MessageHeader.csFigNet/Core/Message.csFigNet/Messages/Operations/RegisterIdentity.csFigNet/Messages/Handlers/RegisterIdentityHandler.csEntangleCommon/OperationCode.csEntangleCommon/Data/Model/EntangleModels.csEntangle/EN.csEntangle/Lobby.csEntangle/NetworkEntity.csEntangleServer/Modules/Lobby/Handlers/*EntangleServer/Modules/Lobby/Components/*