NullTick networking core

Ten thousand matches at once. One server. The server decides every outcome.

Press Play and you're multiplayer — no server to run, no netcode to write. Every feature is one component you drop in.

Talk to us See how it works Running in production SDK not public yet

Every genre, turn-based included. Zero networking code. Two demos come with the Unity SDK — the Playground and Tic-Tac-Toe — built only from drag-and-drop components.

Unity ships today. Unreal, Godot and the browser are in development.

0outcomes a client decides — it sends what the player wants to do, and the server decides what happens
0positions a client can send — the server works out where every player is from their input
1place each stat lives — speed, jump, damage, mass: values the server owns and every player receives

Billed on peak CCU — the most players connected at once. Server-owned NPCs never count.

What you get

Six things that are true before you write a line.

Client InputServer Position

The server decides every outcome

Where everyone is, hits, damage, health, respawns, pick-ups and throws. A client sends what the player wants to do; there is no client-authoritative mode to opt into.

10,000 matches one server

Ten thousand matches at once

Open at once on one server process — measured, not modelled. A match nobody is moving in costs almost nothing.

Tick Event

Events, not ticks — only what moved goes on the wire

A tick loop walks every entity. NullTick touches only the ones that moved. The sleeping half doesn't exist to the CPU.

UI
Gameplay
NullTick Core

Zero networking code

Hover to assemble. Both demos are built from inspector components, not code. Press Play; the server starts itself.

1 Active 10 Between Turns

Every genre, turn-based included

Between turns, a table sends almost nothing. Network traffic is reserved solely for active play.

Unity
Unreal*
Godot*
C ABI

One core, every engine

Unity ships today; Unreal and Godot (*) are in development. Hover to see them call the same flat 16-symbol C ABI.

The world the server runs

Collision, forces and throws — decided on the server.

Physics is part of the server, not something each player's game guesses at. Every player sees the same result because there is only one place it is decided. Motion is worked out in closed form and future contacts are scheduled as events, so a body at rest costs no per-body work.

In the SDK today

  • One collision rule. Players, vehicles, platforms, crates and thrown things all collide the same way, by their shape, their collision layers and their motion — whatever they are doing.
  • Mass. A heavier body pushes a lighter one, players included.
  • Forces. Blasts, knockback and bouncy floors push bodies and can throw a player.
  • Carry and throw. Pick up, carry, drop and throw — the server decides each one.
  • Projectiles. A bow's arrows and a cannon's balls fly an arc set by their stats, can stick where they hit, and do damage.
  • Room objects. A room can own its crates, trampolines and cars, shared by everyone in it.
  • Every stat is a server value. Move speed, jump, mass, throw strength, damage: you set the starting value on the component, the server owns it, and every player receives it — late joiners too.

In short The server decides what happens when things touch. Your players can't disagree about it, and can't cheat it.

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 compute scales with the size of the world, not the amount of play: a map holding 65,000 entities with 400 people moving still walks all 65,000, sixty times a second.

NullTick doesn't optimize that loop. It removes it. Updates are events, so update work 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 touches only the ones that do.

In short A tick walks sixty-five thousand entities to learn that nothing changed. Events touch only the ones that moved.

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.

In short One leak does the deadzone, the filter and the scheduler at once — so jitter and stick drift die as physics, not as a conditional you have to tune.

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
};

In short 48 bytes an entity, and 65,536 of them in a 3 MiB arena — the whole world's network state sits in L3, reached by offset math rather than a pointer chase.

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.

In short One byte of lock per entity and none for the world, so two players in the same room never contend — and a slow socket can't stall the entity it belongs to.

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.

In short Feet slide when the animation clock and the network clock disagree. Here they're the same clock, so there's nothing left to disagree.

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. The Unity SDK ships today; Unreal and Godot bindings are in development, and any other engine can consume it through the flat C ABI. The wire is a versioned little-endian UDP protocol with a runnable reference host.

Engine
Core

C ABI v1

16 symbols, no C++

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.

Engine bindings

Engine SDKs

The Unity SDK binds the native ABI and refuses to load on an ABI mismatch. It carries rooms, reliable messaging, network variables, server-side collision, and an in-editor Hub, and its Local mode starts a server on your machine when you press Play. Unreal (the NullTickRuntime plugin) and Godot (a pure-GDScript client) are in development.

Server-authoritative UDP

No position on the movement wire

A client cannot move itself. It sends input; the server works out the real position from that input, with collision, and tells every player. Speed is a server-owned stat, so there is no coordinate on the movement wire to forge mid-game.

Re-joining can't move you either: a reconnect resumes the server's ledger and ignores any declared start. Taking over a live seat requires that player's own session token. A server-issued 64-bit session token that every input packet must echo blocks spoofing; per-packet input clamps cap speed hacks and floods.

make test_asan PASS
make test_ubsan PASS
make test_tsan PASS

Test discipline

Sanitizer-clean

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.

In short Sixteen C symbols and no C++ on the boundary, so the engine you use is a binding rather than a rewrite — and the same core answers Unity, Unreal, Godot and anything else that can call C.

07 — Get in touch

Tell us what you're building.

We'll demo live, walk the architecture, and share results under NDA.