Nodal Strike

A browser team shooter built to be fair over the internet, and a template for building your own multiplayer game.

A multiplayer shooter that is also a template

Nodal Strike is a browser-based 5v5 team deathmatch shooter, and it is the first game built for Nodal Play. It has two jobs. As a game, it is a quick, readable shooter you can open from a link, play offline against bots, or share with friends through a six-digit room code. As a template, it is a worked example of how to build a multiplayer game that stays fair and smooth over the internet, with the netcode separated from the game rules so it can carry other genres: battle royale, capture the flag, zombies or racing. It was built on stream by vAlentino718 with Claude and Codex agents, and you can play it at nodal-strike.uzig718.workers.dev.

First-person view in Nodal Strike: a rifle in the foreground and an enemy player firing from behind crates in a desert map
In-game view from the Nodal Play launch material: a first-person rifle and an opponent firing from behind cover on a desert map.
60
Hz simulation tick
Server and client prediction step at 1/60 second.
30
Hz snapshots to clients
Delta-compressed binary state.
200
ms lag-compensation window
Hitbox history of 64 ticks per player.
4.3
KB/s per client downstream
Measured on localhost with 10 clients; about 0.5 KB/s up.

What you can do in it

Inside Nodal Play, a streamer’s browser creates a private room with bots named after the room, and everyone in the room is launched into the embedded game after a countdown. The game exposes a small room API and posts two messages to the platform (nodal:match-ended and nodal:exit) so the host can vote on the next game.

How the netcode works, in plain language

Online shooters feel good only if they hide the delay between your keypress and the server’s answer. The project’s netcode document explains the approach used here, which follows well-known techniques from the Source and Quake traditions.

The server decides everything

Clients send only inputs: movement, aim and button presses. They never send positions, hits or damage. The same room code runs in three places: a Cloudflare Durable Object for online rooms, a Node server for local development and long streams, and a Web Worker inside your browser for offline play against bots. That last one matters: offline play uses the real protocol, so playing against bots tests the real code path.

Prediction and reconciliation

Your own movement is predicted locally the instant you press a key. The server periodically sends the last input it processed, plus your exact movement state. The client rewinds to that state, replays the inputs the server has not acknowledged yet and, if the result differs from what was on screen, blends the difference away over about a tenth of a second. Because both sides do the same arithmetic with float32 rounding and quantized view angles, results are bit-identical, and a test asserts zero corrections over four seconds of strafing and jumping. Errors bigger than two meters, such as a respawn, snap instead.

Seeing other players

Other players are drawn slightly in the past, interpolated between server snapshots. The delay adapts to measured arrival jitter and is typically 40 to 70 milliseconds. The clock never warps by more than six percent, and extrapolation is capped at 100 milliseconds so a stall does not send players flying.

Fair hits with lag compensation

Each shot carries the time of the frame the shooter actually saw. The server keeps 64 ticks of player hitbox history, rewinds every target to that moment (clamped to a 200-millisecond window) and casts the ray against head, torso and legs. Aim comes from the server-simulated view angles of the same input, never from the client. Weapon spread is seeded by the shooter, the tick count and the weapon, so the client’s tracers match the server’s hits, and a modified client cannot pick a lucky roll.

Small packets

Snapshots are binary and delta-encoded against the last frame the client acknowledged. Only changed fields are sent, using a bitmask, new entities are sent in full and removed ids are listed. Entity types are declared once as schemas, so adding a flag, a zombie or a vehicle means adding a schema rather than rewriting the protocol. Events such as shots, hits and kills travel in a separate message, tagged with the tick on which they happened so remote tracers leave the gun of the interpolated body.

Why the uplink is 20 Hz

Inputs travel at 20 Hz even though the simulation runs at 60, with each packet carrying every command created since the last one. The reason is cost: the free hosting plan counts incoming WebSocket messages at 20 to 1 against a daily request budget, which works out to about 2.7 hours per day of full ten-player lobbies. On a paid plan or a self-hosted Node server the rate can be raised to 60, and for long streams the project suggests running the Node server behind a free tunnel.

Safeguards against cheating and abuse

The document is honest about the limits. A low-ping cheater could shoot up to 200 milliseconds into the past, because the rewind is not yet clamped to measured round-trip time. Players are simulated in join order within a tick, so same-tick trades favor the earlier player. Weapon spread could be seeded with a server-only value for competitive integrity. WebSocket runs over TCP, so a lost packet stalls everything behind it for one round trip, and a datagram transport would help on hosts that support it. And there is no player-to-player collision, because adding it would cause mispredictions. A list of known weaknesses next to the fixes is more useful than a claim of perfection.

Testing and tools

The repository ships about 180 automated tests across 20 test files covering the codec, delta compression, the input queue, interpolation, deterministic prediction, the room logic, bots and the server adapters. A load-test script drives ten headless clients and reports round-trip time, snapshot rate, bandwidth per client and the largest gap between snapshots. A smoothness script runs the real interpolation code at 144 frames per second and prints discontinuities. In the browser, adding ?netsim=150,20 to the address simulates 150 ms of round-trip delay with 20 ms of jitter, and pressing F3 shows a net graph with ping, jitter, interpolation delay, snapshot rate, bandwidth, corrections and frame-time statistics.

Using it as a template

The template guide has recipes for adding a weapon, a map (including imported GLB maps), a game mode (free-for-all in full), capture the flag as an end-to-end new entity type, domination zones, round-based modes, a zombies co-op mode, a battle royale with interest management and a zone, and vehicles. It also states the rules that keep netcode correct: shared simulation code must be deterministic (no random numbers, no wall-clock time, no frame delta), anything the client predicts goes in the private block of the snapshot, and the protocol version must be bumped whenever the wire format changes. Those rules are the real value of the template: they are the lessons that agents tend to violate when left alone, written down so they stop doing it.

Assets are CC0 or properly credited glTF characters and animation libraries, with a licence checklist in the repository. If you want to see how agents are directed on a project like this, read The vibe-coding workflow, and for the tools that keep several agents from colliding on one repository, see AgentBoard on the Nodal site.

Nodal Play version 0.1 launch end card
Nodal Strike was announced as the first game on Nodal Play v0.1.

Status

Nodal Strike is playable now, offline and online. The platform handoff was tested locally and a two-player live check on the arcade was still pending in the project notes, so expect rough edges. The next idea on the list is a zombies variant built on the same template. Follow along on the livestream or in the Discord.