Repository navigation
Networking Wire Format
This page documents the binary wire format used by SparkEngine's UDP networking layer for all client-server communication.
Source: SparkEngine/Source/Engine/Networking/NetworkManager.h, EntityReplicator.h, ReplicationFields.h
Note: Networking requires
ENABLE_NETWORKING=ONduring CMake configuration.
All packets use little-endian byte order. Every packet begins with the same 23-byte header;
a ReliableOrdered packet (channel 2) carries a 4-byte ordered sequence after it (protocol v3):
Offset Size Type Field
────── ──── ───────── ──────────────────────────
0 4 uint32 Magic (0x5350524B = "SPRK")
4 2 uint16 MessageType
6 1 uint8 ChannelType
7 4 uint32 SenderID (ClientID)
11 4 uint32 SequenceNumber (reliability/ACK)
15 4 float32 Timestamp (server time)
19 4 uint32 PayloadLength (N)
23 4 uint32 OrderedSequence -- ReliableOrdered only
23|27 N bytes Payload
- Minimum packet size: 23 bytes (empty payload; 27 for ReliableOrdered)
-
Maximum payload size: 65,453 bytes on every channel (
MAX_NETWORK_MESSAGE_PAYLOAD_SIZE) -
Magic number:
0x5350524B— ASCII"SPRK". Packets with incorrect magic are silently dropped.
enum class MessageType : uint16_t
{
// Connection lifecycle
Connect = 1, // Client → Server: request to join
ConnectAccepted = 2, // Server → Client: connection approved
ConnectRejected = 3, // Server → Client: connection denied
Disconnect = 4, // Either direction: graceful close
Heartbeat = 5, // Either direction: keepalive
// Reliability layer
Ack = 6, // Acknowledges reliable messages (sequence + bitfield)
// Entity replication
EntitySpawn = 7, // Server → Client: new entity
EntityDestroy = 8, // Server → Client: entity removed
EntityStateUpdate = 9, // Server → Client: delta state
EntityRPC = 10, // Either direction: remote procedure call
// Input
ClientInput = 11, // Client → Server: input state
InputAck = 12, // Server → Client: input acknowledged
// Game events
ChatMessage = 13,
GameStateSync = 14,
MatchStart = 15,
MatchEnd = 16,
PlayerRespawn = 17,
ScoreUpdate = 18,
// Extension point
UserDefined = 1000 // Game-specific messages start here
};Each message specifies a delivery guarantee:
| Value | Channel | Behavior |
|---|---|---|
| 0 | Unreliable |
Fire-and-forget. Used for position updates, movement. No retransmission. |
| 1 | Reliable |
Guaranteed delivery with acknowledgment. Retransmitted until acked. |
| 2 | ReliableOrdered |
Guaranteed delivery in send order. Messages queued until predecessors arrive. |
Entity state is replicated using a field-level dirty bitmask system. Each entity can have up to 64 replicated fields (one bit per field in a uint64_t mask).
Sent when an entity first enters a client's relevance set:
Offset Size Type Field
────── ──── ───────── ──────────────────────────
0 4 uint32 EntityID (network ID)
1 1 uint8 FieldCount
5 8 uint64 AllFieldsMask (visibility)
13 var bytes Field0 data
... var bytes FieldN data
Sent each tick for entities with changed fields:
Offset Size Type Field
────── ──── ───────── ──────────────────────────
0 4 uint32 EntityID
4 8 uint64 DirtyMask (changed + visible fields only)
12 var bytes Dirty field data (in bit order)
Only fields whose corresponding bit is set in DirtyMask are serialized. Fields are written in ascending bit order.
Each replicated field has a visibility level controlling which clients receive it:
| Level | Description |
|---|---|
Public |
All connected clients |
Private |
Owner client only (e.g., ammo count, inventory) |
Party |
Owner's party/squad members |
Spectator |
Spectator clients only |
Fields must be trivially copyable. Supported types:
| Type | Wire Size |
|---|---|
bool |
1 byte |
int32_t |
4 bytes |
uint32_t |
4 bytes |
float |
4 bytes |
XMFLOAT3 |
12 bytes |
Sent from client to server every frame:
Offset Size Type Field
────── ──── ───────── ──────────────────────────
0 4 uint32 InputSequence
4 4 float32 MoveForward [-1, 1]
8 4 float32 MoveRight [-1, 1]
12 4 float32 LookYaw (degrees)
16 4 float32 LookPitch (degrees)
20 1 uint8 ButtonFlags (jump|fire|reload|sprint|crouch)
21 4 float32 DeltaTime (client frame dt)
25 4 float32 Timestamp (client-local time)
The InputSequence is a monotonically increasing counter used for server reconciliation during client-side prediction.
The server maintains a rolling history of entity snapshots for hit verification:
struct HistorySnapshot
{
float timestamp;
struct EntityState
{
uint32_t networkID;
XMFLOAT3 position;
XMFLOAT3 rotation;
XMFLOAT3 boundsMin; // AABB for hitbox rewind
XMFLOAT3 boundsMax;
};
std::vector<EntityState> entities;
};When a client reports a hit, the server rewinds entity positions to the client's timestamp and verifies the shot against historical AABBs.
Reliable messages use a sliding-window acknowledgment scheme:
- Sender assigns a
SequenceNumberto each reliable message (Reliable and ReliableOrdered share this reliability stream). A ReliableOrdered message also takes anOrderedSequencefrom a separate per-peer stream that starts at 1; only that one drives in-order delivery, so Reliable traffic can never leave a gap in the ordered stream (the protocol-v2 bug that stalled every ordered message sent after a Reliable one) - Receiver sends
Ackmessages containing the highest received sequence plus a 32-bit bitfield for the previous 32 sequences - Sender retransmits unacknowledged messages after a configurable timeout
Client Server
│ │
│──── Connect ─────────────────>│ plaintext frame
│ (ClientHello) │
│ │
│<─── ConnectAccepted ──────────│ plaintext frame
│ (clientID, time, ver, │
│ signed ServerHello) │
│ │
│═══► ClientFinished (name) ═══>│ sealed from here on
│ │
│<─── GameStateSync ────────────│
│ (full world state) │
│ │
│──── Heartbeat ───────────────>│
│<─── Heartbeat ────────────────│
│ (ongoing keepalive) │
Protocol version 2 (NET-100) frames every datagram: a first byte of 0x01 marks a plaintext
handshake frame (only Connect, ConnectAccepted, ConnectRejected), and 0x02 marks a sealed
frame (a ChaCha20-Poly1305 SecureChannel packet around the message). The Connect payload is
exactly the 55-byte ClientHello: handshake magic 0x484E5053 ("SPNH"), the uint16
NETWORK_PROTOCOL_VERSION (currently 2), the suite byte, an X25519 ephemeral key and a nonce;
the player name no longer travels in Connect. Before it considers a client slot, the server
rejects a missing (ProtocolMissing), older (ProtocolTooOld), newer (ProtocolTooNew),
malformed (MalformedHandshake) or wrong-suite (UnsupportedSuite) handshake, and it rejects with
ServerFull when every slot is taken; a small-order ephemeral key is MalformedHandshake found by
comparison, before any curve or signature work. ConnectAccepted echoes the version and carries the
server's signed ServerHello; the signature (transcript label SPNH-v3) also covers the 10-byte
accept prefix, so the assigned client id cannot be rewritten in flight. The client checks the server
key against its pin or known_hosts (ServerIdentityMismatch) and the signature
(HandshakeAuthFailed), then sends the player name in a sealed ClientFinished, which is what
admits it. Until then the slot is Securing: it is not listed by GetClients(), and SendToClient
refuses it everything but the handshake answer. Unadmitted Connects are budgeted per source IPv4
address (ConnectRateLimiter; excess is dropped unanswered). A version-1 client's unframed Connect gets
an unframed typed ProtocolTooOld. The exact layouts are in docs/specs/networking-wire-format.md;
the evidence is the CTests NetworkSessionCompatibility (Tests/TestSessionCompatibilityReal.cpp)
and NetworkSecureTransportWired (Tests/TestSecureTransportWired.cpp).
The wire format is transport-agnostic. Two transports are available:
| Transport | Status | Description |
|---|---|---|
UDPTransport |
Active | Raw BSD/Winsock UDP sockets |
SteamTransport |
Stub | Steamworks P2P relay (not yet implemented) |
Transports implement the ITransport interface and are selected via TransportType enum at initialization.
The active UDP path binds to loopback or one canonical RFC1918 interface/prefix and admits only concrete peers in the captured subnet. Missing/invalid prefixes, exact network/directed-broadcast addresses, wildcard/public/test/multicast/limited-broadcast/CGNAT values, mapped IPv6, and alternate textual encodings fail closed. Peer scope is checked before packet deserialization and again on all gameplay send/retry paths. Client traffic is bound to the configured server address and port, and server-side client identity is bound to the endpoint recorded during Connect. Wire-supplied sender IDs are not trusted. Undefined channels, malformed built-in payload sizes, and unauthenticated custom messages are rejected before dispatch.
Since protocol version 2 (NET-100) every datagram after the handshake is sealed with libsodium's ChaCha20-Poly1305 in a SecureChannel keyed by the signed-ephemeral X25519 handshake (Networking); OD-06: libsodium supplies every primitive. The client authenticates the server's Ed25519 identity (a pinned key, or trust on first use recorded in known_hosts), and each sealed frame is authenticated, replay-checked in a 256-packet window, and opened only with the channel of the peer its endpoint belongs to. A plaintext gameplay frame, a frame sealed under another peer's key, and any tampered, replayed, truncated or out-of-epoch frame are dropped and counted (NetworkStats::securityDrops, plaintextFramesDropped). There is no plaintext mode, and a message marked sensitive is refused unless a channel to its destination exists. Send keys rotate every 600 s or 2^31 packets.
The transport does not authenticate players: account authentication runs inside the channel (TERRAFRONT uses SCRAM, so no password crosses the wire). SparkGateway admission tickets are not yet bound to the UDP session; the decided design presents the ticket inside the sealed ClientFinished. The implementation has not had an independent cryptographic review, and anti-amplification cookies are not implemented (a ConnectAccepted is about 2.3 times its Connect; the per-source Connect budget bounds how often one address can trigger it), so NET-100 stays open until both land.
Planned security work includes:
- Gateway ticket presented in
ClientFinishedand checked by the area server - Anti-amplification cookies for
Connect - Independent review of the handshake and channel composition
See Networking for the full networking architecture overview.
Published from 614bca17b41e. Edit the canonical source in wiki/.
- Documentation
- Docs route
- Wiki index
- Guides
- Tutorials
- Samples
- Examples
- API Reference
- API route
- Reference
- Build Guide
- Dependencies
- FAQ
- Changelog
- Roadmap
- Contributing
- Code of Conduct
- Home
- FAQ
- Getting Started
- Quick-Start Tutorial
- Making Your First Game
- Making Your First Multiplayer Game
- Artist Workflow Guide
- Editor Walkthrough
- Migration Guide
- How SparkEngine Works
- Architecture Overview
- Engine Architecture Flowchart
- Creating a Game Module
- Game Modules (catalog)
- Entity Component System
- Rendering and Graphics
- Physics
- Cloth Simulation
- Audio
- Input System
- Camera System
- Scripting with AngelScript
- Visual Scripting
- AI and Navigation
- Animation
- 2D Systems
- Networking
- Dedicated Server
- Multiplayer Quick Start
- Area Server Architecture
- Scene Management
- Large World Support
- Collaborative Editing
- Coroutine System
- Event System
- Event Response System
- Job System
- UI System
- UI Layout Extensions
- Localization
- Dialogue System
- Destruction System
- Replay System
- Achievement System
- Loading System
- Mod System
- Content Delivery
- Tween System
- Memory Integrity
- Gameplay Systems
- Terrain and Procedural Generation
- Save System
- Persistence System
- Day Night Cycle and Weather
- Cinematic Sequencer
- Runtime Prefabs
- SparkEditor
- Editor Tutorials
- SparkConsole
- SparkDaemon
- Shader Pipeline
- Asset Pipeline
- Asset Validation
- Asset Migration
- Game Packaging
- Online Services
- DataTable System
- Loot and Crafting System
- CSG System
- Font System
- Timer Manager
- Movie Render Pipeline
- HLOD and World Partition
- Remote Debug System
- Selection Manager
- Asset Dependency Graph
- Editor Automation
- File Watcher
- Project Templates
- System Requirements
- VR Support
- Mobile Platform
- Accessibility
- Platform Input
- Platform Certification
- Cross-Compilation: Wine Testing
- RHI Abstraction Layer
- D3D11 Backend
- D3D12 Backend
- Vulkan Backend
- OpenGL Backend
- Metal Backend
- DXR Raytracing
- Hybrid Ray Tracing
- Upscaling (DLSS/FSR)
- Render Graph
- Shader Graph
- GPU Particles
- GPU-Driven Rendering
- Volumetric Fog
- Volumetric Clouds
- Global Illumination
- Virtual Texturing
- Water Rendering
- Clustered Lighting
- Material System
- Post-Processing
- Shadow System
- Particle System
- Decal System
- Sky and Atmosphere
- Foliage System
- Mesh Shaders
- Neural Rendering
- Configuration Reference
- Performance Tips
- Benchmark Framework
- Threading Model
- Fuzz Policy and Parser Security
- Memory Safety
- Memory Management Patterns
- Build System and CMake Modules
- Profiler and Debugging
- Performance Profiling Guide
- Telemetry System
- Crash Reporting
- Golden Image Testing
- Utilities
- Testing
- Fuzz Policy and Parser Security
- Codebase Statistics
- Codebase Health
- Error Handling Patterns
- Hot Reload Overview
- Troubleshooting
- Contributing
- Workflow Patterns
- Build Optimizations
- CI Reproducible Builds
- CI Controlled-Failure Rehearsal
- Release Publication Stages
- Session Handoff 2026-09-27
- GitHub API and PR Checks
- Git Rebase Conflicts
- Clang-Format
- Code Quality Violations
- AI Bloat Pattern
- MinGW + Wine Cross-Compilation
- Live Editor Testing
- Engine & Renderer Landscape
- DuetOS Portability Catalog
- Five-Engine Analysis
- Eleven-Engine Analysis
- ThorVG / Unity Graphics Analysis
- Advanced Techniques Catalog
- Third-Party Library Evaluation
- Engine Viability Evaluation
- Engine Feature Recommendations
- Project Recommendations
- Mac Compatibility Analysis
- Codebase Observations
- Codebase Bloat Audit
- Test Suite Audit
- Documentation Coverage Audit
- ThirdParty Dependencies Audit
- Load Test Baseline
- Gameplay Systems Status
- SparkGame Module Status
- Stub and Abandoned Features
- Memory Integrity System
- Memory Safety Evaluation
- Hardware Acceleration Systems
- Jolt Physics Integration
- GPU/CPU Separation Plan
- Daemon Services Architecture
- Online Service Boundary
- Server Operations Runbook
- Reflection & Polymorphism Refactoring Plan
- SparkBuild In-Tree
- Wine No-JobSystem Breakthrough
- Wine Role and Fallback Tiers