The trail › Neon Brawlers

How Neon Brawlers stays fair at a quarter-second of lag

Neon Brawlers is a fighting game for two to four players on phones. I've paused work on the game itself, but its online side is finished and tested. It's the part I learnt the most from, so this page shows how it works and how it held up.

Game on hold · netcode finished and tested
[CAPTURE] Core Rush, four players online
0 framesof delay on your own moves. They show on the next frame even at 250 ms ping.
26 of 26test matches ended with every player's game in exactly the same state as the server's.
338 bytesper update, on average, in a real match from Pakistan to a server in Germany.
252 msto reconnect on its own after the connection drops.

How it works

The server runs the real game. Phones send what the player pressed, and the server sends back what happened. The trick is hiding the time that takes.

Your phone
1Sends your buttons 60 times a second. Each press is 5 bytes, and every packet repeats the last 8, so one lost packet costs nothing.
4Moves your fighter straight away instead of waiting. When the server's answer arrives, it replays anything the server hasn't seen yet. Small differences fade out; big ones (over 48 px) snap.
5Shows the other players 100 ms in the past, sliding smoothly between updates.
The server
2Runs the one true copy of each match at 60 ticks a second. The simulation is deterministic: the same inputs always give the same result.
3Sends back only what changed since the last update you confirmed, then compresses it. Hits and pickups ride along until you confirm them, so a lost update never loses a hit.
+Keeps hits fair without rewinding time: targets get a slightly bigger hitbox as ping rises, from 1 px up to at most 6 px.

Pick a lag level

I tested full matches through a lag simulator that adds delay, ±15 ms of jitter and 1% packet loss. Eight matches ran at each level.

With 250 ms ± 30 ms and 3% loss, full matches still ended with matching game states.

Measured ping149 to 152 ms
Average update size315 to 413 B
Ticks your phone replays each frame5.2 to 5.6
Hits or pickups shown twice0 of ~2,900
End state matched the server in 8 of 8 matchesOther players froze for 0 to 1 frames per match

A real match over the internet

Two PCs in Pakistan played a full match (5,441 ticks) on the live server in Germany. Ping sat at 143 to 156 ms, with one real spike to 296 ms.

  • Other fighters never froze, and moved with under a third of a pixel of jitter.
  • Updates averaged 338 bytes. Each phone downloads about 7 KB a second.
  • Both players' games ended on the server's exact state.

When the connection breaks

Connection drops
Reconnects by itself in 252 ms.
3 s of silence
A bot holds your seat after 1 second. You're back in control 632 ms after the signal returns.
App closed
A bot takes over after 1 second. You're back in the match 538 ms after reopening the app.

How I tested and shipped it

  • A lag simulator that adds delay, jitter and packet loss on demand.
  • Headless bot players that check the final game state against the server.
  • Two-window playtests that measure input delay and can cut the signal or kill the app.
  • One script builds the Linux server and smoke-tests it; another deploys it with a health check and automatic rollback.

Not tested yet

Many rooms at once. My estimate is about 13 CORE RUSH matches per CPU core, but I haven't run a load test. I also haven't timed the netcode on a real phone yet.

Fresh Powder's server re-runs every sled run through the game's own simulation before it counts. That's this idea again.

Next stop: Fresh Powder