This repo implements a transport-agnostic snapshot + delta codec for realtime state synchronization. It focuses on: bit-packed encoding, quantization, baseline/ACK-driven delta compression, and correctness-first decoding.
The codec is bytes in / bytes out. It does not open sockets, manage connections, or assume any game engine/ECS.
-
Correctness and safety by default
- Bounded decoding (no panics, no unbounded allocs, no OOM amplification).
- Explicit limits and predictable memory usage.
- Deterministic results for a given input ordering.
-
Engine and transport agnostic
- No dependency on UDP/ENet/QUIC.
- No ECS types or engine-specific assumptions.
- Caller supplies entity ordering and relevancy decisions.
-
Pragmatic performance
- Bit-packed payloads; quantization for common numeric fields.
- No steady-state allocations in hot encode/decode paths (buffer reuse).
- Optional tracing/introspection behind feature flags.
-
Evolvable wire format
- Versioned framing with room for additive extensions.
- Schema identity to prevent silent decode mismatches.
- Client prediction / server reconciliation / lag compensation.
- Encryption, authentication, NAT traversal, matchmaking.
- A full "network framework" (channels, reliability, resend). Transport layer is external.
This is a single git repo using a Rust workspace. Split into crates to keep boundaries clean while iterating quickly.
Responsibility: low-level bit packing primitives.
BitWriter: write bits, aligned ints, varints.BitReader: bounded reads, exact error reporting.- Primary API is bounded (writes into
&mut [u8]);BitVecWriteris a convenience wrapper. - Utilities for fixed-point quantization encoding.
- No domain knowledge (no entities/components).
Notes
- Aim for
#![forbid(unsafe_code)]in early releases. Revisit only with proof from profiling. - Make bounds checks explicit and exhaustive.
Responsibility: wire framing and canonical binary layout.
- Packet header encode/decode.
- Section tags and framing; section bodies are returned as byte slices.
- Wire-level limit checks only (packet/section bounds).
Notes
wiredoes not know about the game state types—only the structure of the packet.- Keep the format boring and stable.
Responsibility: represent replication schemas and field codecs.
- Runtime schema model (initial release).
- Optional
serdesupport for JSON import/export (for tooling). - Deterministic
schema_hash. - Field codec descriptors:
Bool,UInt,SInt,VarUInt,VarSIntFixedPoint(bounded, precision)Angle(bounded, wrap-aware) — optional later
- Field policies:
- quantization config
- change threshold config (for delta emission)
Notes
- The initial release is runtime-first. Derive macros can come in a later release.
- Avoid runtime reflection on arbitrary Rust types in the initial release; keep schema explicit.
Responsibility: snapshot/delta logic.
- Build/encode full snapshots and deltas.
- Apply deltas to a baseline to reconstruct a new snapshot.
- Baseline history store (ring buffer) and baseline selection helpers.
- Change detection and per-field/per-component masks.
- Dual update encodings (masked vs sparse) with encoder-side selection.
Key types
SnapshotTick(u32)EntityId(u32 in the initial release; widenable later with a type alias)Schema(fromschema)CodecLimits(hard bounds used by codec and wire)
Notes
codecis where most invariants live. Keep them documented and tested.
Responsibility: introspection / debugging tools.
sdec-toolsCLI withinspectanddecodecommands.- Decode a packet and print structure or JSON.
- Explain packet size by section/component/field (feature-gated tracing).
- Diff baseline vs current (uses decoded representations).
Notes
- Tooling is a major adoption lever—treat it as a first-class product.
- Tools reuse codec decode helpers (e.g.,
decode_delta_packet) rather than re-implementing parsing.
Responsibility: replication graph and interest management.
- Per-client relevance filtering and change list generation.
- Produces
creates/destroys/updatesforcodec::encode_delta_from_changes. - No transport or ECS dependency; caller supplies world data via callbacks.
Responsibility: reproducible scenarios for size/perf/robustness.
- Deterministic scenario generators (movement, bursts, spawns).
- Loss/reorder simulation to verify resync behavior.
- Simbench harness emits
summary.json(bytes/timing) and baseline comparisons.
Responsibility: reference demo schema + deterministic capture generator.
demo-schemadefines a small FPS-ish schema and state representation.demo-simruns a deterministic simulation and emits captures +summary.json.- Used in CI to catch regressions in packet sizes and correctness.
- The codec interfaces should accept:
- the schema,
- a baseline reference (optional),
- a “state view” provided by the caller (iterators/callbacks),
- and output into caller-managed buffers.
To avoid steady-state allocations:
- Provide
CodecScratchthat the caller owns and reuses. - Encode functions accept
&mut Vec<u8>or&mut [u8]and never allocate internally except in explicitly documented cold paths.
The codec should be deterministic given:
- stable entity ordering and stable component ordering. Prefer:
- caller supplies stable ordering, or
- codec sorts with caller-provided scratch (no heap).
These invariants must hold for all released versions:
-
Decode never panics
- All parse errors are returned as
Result::Errwith structured errors.
- All parse errors are returned as
-
Decode is bounded
- Length prefixes are validated against limits before iteration and before any allocation.
- All reads are bounds-checked against the input slice length.
-
No amplification
- A small packet must not cause large allocations.
- All allocations (if any) must be capped by
CodecLimits.
-
Schema mismatch is explicit
- Packets include
schema_hash; mismatch fails fast.
- Packets include
-
Wire format is versioned
- Backwards-incompatible changes require a version bump.
- Additive changes should use extension mechanisms where possible.
Every PR must add tests proportional to the surface it changes.
- Unit tests (
bitstream,wire). - Golden vectors for wire stability (
wire+codecminimal fixtures). - Property tests for round-trips:
- full snapshot encode/decode round-trip
- delta apply correctness:
apply_delta(baseline, encode_delta(baseline, current)) == current(within quantization)
- Fuzzing targets for decoding:
- packet framing decode
- delta apply decode paths
- Chaos tests in
simbenchfor loss/reorder recovery.
denyunsafe in early releases.clippy -D warnings,fmt,cargo testin CI.- Fuzz targets must compile in CI (running fuzz in CI can be periodic).
wire.versionis the on-the-wire compatibility contract.- Crate semver is secondary to wire compatibility; still follow semver.
- Document wire version changes in
WIRE_FORMAT.mdchangelog section.
We stay flexible by:
- keeping the header minimal,
- defining a small set of section types now,
- reserving extension points via optional sections and flags.
We do not include speculative fields “just in case.” Everything in the initial release is justified by immediate delta snapshot needs.