SN8KE: building a 3D LiveOps multiplayer snake on 2048 rules
- Unity
- Multiplayer
- Nakama
- Netcode
- LiveOps
- Architecture

SN8KE is a 3D multiplayer PvP snake: think agar.io or slither.io, but the thing you're actually managing isn't length — it's a 2048 board wearing a snake costume. Unity client on WebGL, Nakama as the entire backend, server-authoritative from the first line of match code.
This post is about what the game actually does, and the handful of architecture decisions that made it buildable without turning into a debugging nightmare.
Raw gameplay from first week of development
The hook: your snake is a 2048 board
Forget length as the win condition. Here's what actually matters:
- You spawn as a single block — a head with value 2.
- Food scattered on the map is just more blocks with values from an active set — starting at 2 and 4, with 8 and 16 (and beyond) unlocking later in the round.
- You can eat anything whose value is ≤ your head's value. Food, an enemy's tail segment, an enemy's head — same rule, no exceptions.
- Eaten blocks get inserted into your tail at the correct sorted position (tail is always sorted descending by value), sit there for about a second, then merge with an equal neighbor if one exists — cascading like 2048, just delayed by that one-second window so an opponent has a real chance to bite the fresh block off before it locks in.
The part that actually creates PvP tension: danger is your head's value, not your length. A head worth 128 with zero tail is scarier than a head worth 64 dragging twelve blocks behind it. Head-to-head, bigger value eats smaller; equal values just bounce off each other — nobody dies. Bite an enemy's tail and everything behind the bite point falls off and turns into food on the spot. Lose your head, and your entire tail scatters the same way.
That single rule — value comparison, applied uniformly to food, tails, and heads — is doing all the game design work here. No separate systems for "eating food" vs "PvP combat." It's one rule, checked in different contexts.
Why the server owns literally everything
The architecture decision that shapes the rest of this post: Nakama is the only source of truth, for the match and for the meta. Not "the server validates the client's claims" — the server runs the whole simulation, and the client is a very good renderer with an opinion about what's about to happen.
Movement, merging, collisions, death, food spawning — all of it runs on a 20Hz tick loop inside Nakama. The Unity client sends input, predicts its own head's movement so it doesn't feel laggy, and draws whatever the server actually decided. It has no write access to anything that matters: inventory, equipped skins, and match outcomes are read-only from the client's perspective, enforced at the storage layer (permissionWrite:0), not just by convention.
Why go this hard on authority for a snake game? Because the moment two snakes can eat each other, "trust the client" turns into "let players decide who wins fights."
The netcode trick: don't sync the tail
Here's the detail that makes this performant instead of a bandwidth fire: tail positions are never sent over the network.
The server only transmits, per snake, per snapshot: head position, head direction, and the array of block values. That's it. The client reconstructs the entire tail's positions itself, from a trail buffer built out of the head's position history — the same "body follows where the head has been" trick every slither.io clone uses, just applied here to save bytes rather than for the visual effect.
A snake with 40 tail blocks costs the same to sync as a snake with 2. The length people build up over a match — which in this game can get long — is effectively free on the wire.
On top of that: your own head uses client-side prediction with reconciliation and error smoothing, so input feels instant even though the server hasn't confirmed it yet. Everyone else's snake is rendered from a snapshot-interpolation buffer, deliberately drawn about 100ms in the past, so their movement stays smooth instead of snapping between server updates. Standard stuff if you've built netcode before, but worth naming because it's the reason the game doesn't feel like it's fighting its own ping.
The part that makes the netcode actually work: one simulation, two languages
Client-side prediction only feels right if the client's guess and the server's eventual answer agree almost all the time. If they disagree constantly, you get visible snapping every time the server corrects you — which is worse than no prediction at all.
The fix here is structural: the simulation core — movement, merge timers, cascade logic, collisions — is written once in C# with zero Unity dependencies (SN8KE.Simulation), and ported line-for-line into TypeScript (server/src/sim.ts) to run inside Nakama's Goja runtime. Same formula, same order of operations, on both sides. The server consumes exactly one input per player per tick, and the client predicts against that exact same constraint. Skip that constraint and you get fractional-step drift that shows up as permanent micro-jitter — no amount of interpolation polish fixes a math mismatch.
It also means changes to game feel have to be made twice, once per language, which is the actual cost of this approach. Worth it here because the alternative — prediction and authority disagreeing on the fundamentals — is a worse bug to live with than duplicated logic.
Client architecture: a stack of assemblies that can't cheat and see each other
The Unity side is split into layered assemblies, each one blind to what's above it:
SN8KE.Simulation — pure C# core, no Unity, portable to TS
SN8KE.Net — match contracts (IMatchClient, snapshots, events)
SN8KE.Net.Nakama — the actual Nakama SDK implementation
SN8KE.UI — popups, state machine, loaders
SN8KE.View — match rendering, skins, block pooling
SN8KE.Meta — menu / shop / profile (MVC)
SN8KE.App — bootstrap, root DI scope
The rule that keeps this from turning into spaghetti: nothing lower in the stack knows anything above it. When the match view needs to report live quest progress up to the meta layer — kills, food eaten, that kind of thing — it can't just reach up and call into SN8KE.Meta directly, because Meta already depends on View for the snake preview, and a reverse reference would create a cycle. The fix is a small interface (IQuestsProgressSink) living in the lowest common ancestor assembly (Net), implemented by whoever needs to consume it. One extra interface, zero cycles.
Skins work on the same "server doesn't care about visuals" principle as everything else: each skin is a single ScriptableObject loaded through Addressables, addressed by its own ID, so adding a new skin means adding a new asset — no rebuild, no shared catalog file to merge-conflict over. The server's entire knowledge of a skin is its ID, slot, and price.
What broke last time, and what that bought this time
Worth being honest about the thing that actually drove most of these decisions: this isn't the first attempt at a multiplayer game like this. A previous project died at the seam between two different networking layers stitched together (Master Server Toolkit + FishNet) — the kind of bug class where "menu didn't transition to game" has two possible owners and you spend an afternoon figuring out which one is lying.
The response, this time, was to remove the seam entirely. One backend (Nakama) owns accounts, inventory, wallet, matchmaking, and the authoritative match — not four services that all need to agree with each other about game state. The server doesn't know what a "menu" or a "scene" is, only match IDs and storage keys, so the entire bug category of "which system thinks we're in which state" is structurally not available anymore. It's not a cleverer fix — it's fewer moving parts to get out of sync in the first place.
Where it stands
Core loop, merge mechanics, server-authoritative matches, prediction/interpolation netcode, skins, an economy running on Nakama's built-in wallet, and a daily-quest system are in. Survival mode is scaffolded but not implemented, matchmaking balance is still being tuned, and the slot-machine gambling tab is a placeholder waiting for its actual mechanic. All needed for a propper release is here in SN8KE v1.0.0!
Available right here in your browser via this link: snake.ashtrickz.pro
More on the netcode internals and the economy/quest backend in a follow-up post, once there's footage worth showing instead of just describing.