DevLog #5 How I Built RiftNet & the SyncServer (No Illusions, Only Signal)
Premise: build the transport I can trust. No outsourced truth, no visual band-aids. Compress, encrypt, acknowledge; then ship the bytes the server actually simulated.
🧱 Architecture in One Pass
- Raw sockets → Channel → Reliability → Compressor → Packer
- Channel: X25519 key exchange. Payloads stay blocked until keys are derived. Compress then encrypt.
- AEAD: pluggable; AES‑GCM by default via libsodium.
- Reliability: custom reliable UDP with seq/ack, sliding window, dedupe.
- Timing: RFC‑6298 RTT estimator (srtt, rttvar, rto) driving retransmits.
- Diagnostics: log every window: inputs, snapshot_ms, dispatch_ms, baseline/delta counts.
🔐 Handshake & Keys
- Client/Server exchange ephemeral X25519 pubkeys.
- Derive sharedTx/sharedRx keys. Until that’s set, any reliable/encrypted message is rejected.
- All pre-secure payloads (e.g., HELLO) live on a tiny side queue then drain post‑init.
📦 Packet Layout & Order of Operations
- Serialize minimal header (flags | seq | ack | ackBits | nonce).
- Pack delta payload(s) against the active baseline.
- Run LZ4 on the payload segment.
- Seal with AEAD (AES‑GCM) using the channel’s nonce discipline.
- Send via UDP. Receiver verifies, inflates, applies, acks.
🕒 The Loop (SyncServer)
- Ingest inputs for tick
T. - Advance simulation (authoritative).
- Produce snapshot: baseline (periodic) + delta (per‑tick).
- Dispatch to clients; update reliability state.
Target cadence: 200 Hz (DT_US = 5000).
🧮 Determinism Guard
For every received frame, compute a canonical FNV‑1a hash on serialized entity fields after sorting IDs. Any divergence trips alarms in test—no “close enough.”
🧵 Concurrency Notes
- Encryption/compression run on a worker path; hot structures are
std::atomicguarded; minimal mutex spans. - Per‑client send rings to avoid cross‑talk; preallocated buffers to keep the allocator cold.
- Back‑pressure when a client falls behind; reliability takes precedence over stuffing more deltas.
📊 What the Numbers Said
- Snapshot build: ~2–3 ms @ modest entity counts.
- Dispatch: < 0.5 ms per window.
- Frame size: ~26–94 bytes depending on movement/delta density.
- Scale: 50 → 500 → 5,000 connection soak (local env) without reliability collapse.
🌐 Beyond One Box
Spec’d a GlobalShardSyncServer to gossip shard truths. Implementation lands after zones/spawns, once there’s something worth gossiping.
Why It’s Built This Way
- Truth first: server is the source of truth; clients draw what the server simulates.
- No illusions: no hidden TCP, no jitter make‑up stories. Reliable UDP, explicit.
- Security native: crypto/compression aren’t add‑ons; they’re in the pipeline.