< Back to Portfolio

THROB

A gritty, hyper-kinetic sci-fi horror FPS where movement drains your veins.

Evolving "doommmmm": Building a Server-Authoritative FPS

THROB started as a port of an older project I built called "doommmmm." But simply moving the code over wasn't enough; it needed to evolve from a disconnected prototype into a fully synchronized, competitive multiplayer experience.

The goal was ambitious: build a raw, server-authoritative multiplayer First-Person Shooter from scratch. No pre-built netcode solutions—just a custom Java backend and a JavaScript/Three.js frontend communicating over WebSockets. Here is a look into the engineering hurdles of building an FPS network architecture.

The 40Hz Jerkiness & The Interpolation Fix

The most critical bottleneck early in development was the classic multiplayer ghosting effect. Players were stuttering across the screen.

Initially, the server was updating absolute positions at a 40Hz tick rate (every 25ms). The jerkiness wasn't a teleportation bug; it was the micro-stutter of clients rendering faster than the server could update them. To fix this, I completely revamped the network synchronization. I bumped the server tick rate to 60Hz (every ~16ms) and, crucially, started broadcasting velocity vectors alongside absolute positions. This allowed the JS client to smoothly interpolate the player's position between ticks. The result? Buttery smooth movement that rivaled standard Node.js implementations, even at 50-100ms latency.

Blood is Health: Designing the Core Mechanic

I wanted to replace the generic "health bar" with something that inherently tied into tactical movement.

In THROB, Health is Blood. Every player spawns with 300 blood points, and your movement directly dictates your survival. Sprinting aggressively drains your blood (-5/sec), while sneaking mitigates it (-1/sec). Standing idle stops the drain. The server strictly infers this movement state from your input velocity every tick, preventing client-side cheating.

During testing, a bizarre edge case emerged: if a player perfectly timed their stamina drain to hit exactly 0, they achieved temporary immortality. The playtesters loved the high-risk, high-reward nature of this exploit, so instead of patching it out, I intentionally kept it as a feature.

Eradicating the DOM

For the HUD, relying on HTML/CSS DOM overlays was causing performance hits and visual desyncs.

I took the extra time to eradicate the DOM from the gameplay loop entirely. The death screen, ammo counter, crosshair, and the dynamic blood meter (which shifts from green to red as you bleed out) are all drawn natively onto the Canvas 2D context. This unified the rendering pipeline, making the UI feel like a seamless part of the game rather than a website overlaid on top of a 3D scene.

The Java Backend Architecture

An FPS is only as good as its server. The backend was constructed in Java using ExecutorService and ConcurrentHashMap to handle isolated, concurrent match rooms efficiently.

It operates on a strict server-authoritative model. The server explicitly controls all player positions, hit validations, projectile tracking, and event broadcasts (like the kill feed). Meanwhile, the client runs prediction and reconciliation algorithms to ensure shooting and movement feel instantaneous to the player, while the server silently corrects any discrepancies in the background.

The match loops are tight: 10 minutes, or first to hit the kill threshold, resulting in a fast, lethal, and brutally strategic shooter.

Key Highlights

Server-Authoritative Netcode60Hz Tick Rate InterpolationConcurrent Match Rooms (Java)Canvas-Native HUD RenderingDynamic Blood-Loss Mechanics