DiscLike

A Discs-of-Tron arena battler whose actual subject is netcode — one deterministic simulation, imported by both the client and the server, so prediction can never drift from truth.

Play it: disclike.bmux.sh — the hub and the training lane are live; the arcade still lists it as coming soon.

The hub: a blueprint mannequin on a dimensioned floor, stamina bar, queue counter, training prompt

What it is

Throw, catch, deflect, build your disclord. Two players on a rectangle, a disc between them, and a shield you hold rather than tap — with a perfect-parry window at the front of the hold and an exhaust state at the back of it. Rounds are best-of-three with an overtime that shrinks the floor until somebody loses.

The arena is drawn like a blueprint: dimensioned walls, a surveyed grid, and an 11-layer stroke mannequin instead of a character model. Nothing in it is a texture — the whole look is line work that un-draws and re-draws itself on hits, which is the same trick as the hit-stop, just spent on the figure instead of the clock.

The DiscLike title screen — name entry and Enter Discworld

The actual project

Real-time multiplayer is where hobby games quietly give up. The usual compromise is to let the client be a bit right — move locally, tell the server afterwards, paper over the difference. It works until two people disagree about who got hit, and then it never works again.

This one takes the hard line. The server runs a fixed-step simulation at 60 Hz and its word wins. Clients send nothing but tick-stamped inputs. They predict locally, interpolate everyone else, and when a late input arrives the server rewinds up to 200 milliseconds, replays the affected ticks, and rescinds anything that turned out not to have happened.

The thing that makes that tractable is a constraint held from the first commit: one pure sim/ module — no dependencies, no wall clock, no Math.random — imported unchanged by both sides. Prediction can’t drift from authority because prediction is authority, running a tick early on a different machine. Every constant lives in a file transcribed verbatim from the design doc, so the thing the designer wrote and the thing the server runs are the same numbers.

The rest follows from that. Runs replay from a seed with per-tick hashes. Two bots can play each other headless, at speed, for benchmarking. Every acceptance item from the design doc that can be checked without a screen is a test named after it.

The audio is synthesised too — about ninety sound ids built from oscillators, no sample files, with a spatialiser and ducking over the top.

Where it stopped

Sprint five, partway through — hardening, performance budgets, and design-review fixes — and then it paused. That’s the honest status. The hub is live, the training lane teaches you the controls, the netcode does what it says, and the roguelike run upgrades that were supposed to build toward a persistent server-side character are still a design doc.

It stopped at the point where the interesting problem was solved and the remaining work was content. That is a real pattern in this list and not a flattering one — the parts that teach you something get finished, and the parts that make a game get a sprint plan.

Technical details

SimulationPure TypeScript, fixed 60 Hz, deterministic, zero deps
AuthorityServer-authoritative, rewind-and-resimulate, 200 ms window
ClientVite + Canvas2D, full-sim prediction, remote interpolation
Transportsocket.io, tick-stamped input records
ServerExpress, per-tick state/input/event history, SQLite token identity
Audio~90 synthesised sound ids — no sample files
ToolingSeeded replays, headless bot-vs-bot benchmarks, netsim