NullTick networking core
Netcode that costs nothing when nothing happens.
NullTick is a drop-in multiplayer core for your game engine — for shooters and action games, and just as much for turn-based, card, and social games. Idle players and quiet matches cost almost nothing, so one server carries far more of them: you pay when players act, not while they wait.
Built to drop into your engine
Three genres, zero networking code. Survival, farming, and turn-based tactics — all built from drag-and-drop components, no netcode written.
Built on Unity, Unreal, and Godot alike.
Billed on player activity — movement or gameplay state changes. Idle presence, server-owned NPCs, and chat are free.
01 — The tick
Sixty times a second, the server asks if anything happened.
The classical loop iterates every entity on a fixed clock — evaluating state deltas, checking deadzones, serializing floats — mostly to learn that nothing changed. The cost scales with the size of the world, not the amount of play: a map holding 65,000 entities with 400 people moving still pays for 65,000, sixty times a second.
NullTick doesn't optimize that loop. It removes it. Updates are events, so update cost is O(Nactive) — the sleeping half of the map doesn't exist to the CPU, and it doesn't exist to the network either.
This holds against even a well-built tick server: a competent one skips sending unchanged positions, but the loop itself still pays a fixed compute cost for every entity on every tick — walking the whole list sixty times a second whether or not anything moved. NullTick pays only for the ones that do.
02 — The physics
A node is a deadzone, a filter, and a scheduler — for free.
Each entity is an event-driven node holding three state values, one per axis. Kinetic input — mouse deltas, stick vectors — arrives and the node charges toward a threshold and decays toward rest, resolved retroactively at the exact microsecond an event arrives. No clock anywhere — decay is applied on packet arrival from the node's stored timestamp. The decay is the deadzone: jitter, stick drift and packet bunching decay to nothing as physics.
When an axis crosses threshold it emits a discrete output, +1 or -1, and resets to zero on the spot. Hold the stick below the bound and the node coasts forever, never costing a packet.
03 — Memory
The whole world's network state fits in L3.
The router is one flat arena — nodes indexed directly by entity ID. No hash maps, no pointer chasing, no per-entity heap. Each node holds state values, threshold, timestamp, and bookkeeping — compact enough that the full state table stays resident in a modern CPU's L3 cache. The size is load-bearing; it does not grow casually.
Stale packets die at the door.
Packets outlive entities: a client can fire at an ID the server just recycled. Every registration bumps the node's generation counter, and every packet carries the generation it was aimed at. A mismatch is dropped at the threshold of the critical section, before any math runs.
struct EntityHandle { uint32_t id; // arena slot, dense uint32_t generation; // fenced against recycled slots };
04 — Concurrency
One spinlock per entity. None for the world.
route_packet takes no lock on the arena — a handle resolves to its node by offset math, O(1). All synchronization is a per-node spinlock, with _mm_pause / yield backoff so contended cores don't saturate the coherency bus. Events fire outside the lock, so a slow network write never stalls the node.
05 — Cadence
Animation phase-locked to the server's rhythm.
Continuous movement makes a node trigger rhythmically — the receiving client hooks events directly into root motion, so every arrival steps the skeletal phase forward by a fixed fraction.
Because playback advances only on event arrival, interpolation delay can't smear a character's feet — foot-sliding requires the animation clock and the network clock to disagree, and here they are the same clock. If the network goes silent, velocity and animation freeze on the exact same phase step for every client.
06 — The engine boundary
A C ABI on one side, a UDP socket on the other.
The core is a small header library with one compiled translation unit and no I/O — it embeds in any engine. Unity, Unreal, and Godot ship first-class bindings today; every other engine consumes it through the flat C ABI. The wire is a versioned little-endian UDP protocol with a runnable reference host.
13 symbols, no C++
C ABI v1
An opaque NullTickRouter*, POD handle and event structs, flat nulltick_* functions. No exceptions or std::function cross the boundary — the smoke test compiles with gcc, not g++, to prove it. Callers verify nulltick_abi_version() before trusting a binary.
Three engine SDKs
Engine bindings
Unity and Unreal bind the native ABI — Unity as a GameObject path plus a Burst-compiled ECS path, Unreal as the NullTickRuntime plugin — each running embedded or networked and refusing to load on an ABI mismatch. Godot ships too, a pure-GDScript client. Every SDK carries rooms, reliable messaging, netvars, and an in-editor dashboard.
No position on the wire
Server-authoritative UDP
Players never report where they are — kinetic deltas in, discrete signals out, and the server owns position, so teleport and position-warp cheats are impossible by construction, not by validation. A server-issued 64-bit session token that every packet must echo blocks spoofing; per-packet input clamps and per-entity rate budgets cap speed hacks and floods; rooms route many isolated worlds through one host. The framed, versioned codec rejects malformed and downgraded datagrams.
Sanitizer-clean
Test discipline
The suite runs under ASan, UBSan and ThreadSanitizer. One test fires exactly 1,000 events from 100,000 interleaved injections — a broken lock loses the count. Explicit-timestamp overloads keep every decay assertion deterministic.
07 — Where it stands
Experimental, and labelled that way.
NullTick is a working core with a running reference server — not a hardened product. Everything gets named by where it actually is.
Running today
- Core router with retroactive update and generation fencing
- Bounded MPSC event queue with overflow accounting
- UDP reference host — loopback round-trip runs in CI
- Server-authoritative security — no client-reported position, session-token anti-spoofing, input clamps and rate budgets (docs/SECURITY.md)
- C ABI with a gcc-compiled smoke test
- Unity binding — GameObject and Burst ECS paths, EditMode-tested, Windows + Linux plugins
- Unreal plugin — NullTickRuntime module, BuildPlugin-verified
- Godot SDK — pure GDScript, networked-mode, in-editor test dock
- Unit + concurrency suite, clean under ASan, UBSan and TSan
Not yet
- Multi-room hosting & lobby — wire protocol support landed; the host feature is in progress
- Connection eviction and HELLO anti-amplification cookies (tracked in the threat model)
- Windows server host — the native plugin already ships on Windows, but the reference UDP host is POSIX-only
- Engine-ID slot allocator — dense-ID mapping is the caller's job today
Building a world where idle should be free? We'll demo it live.
08 — Get in touch
Tell us what you're building.
We'll demo live, walk the architecture, and share results under NDA.