UT2004, Forever

March 16, 2026 9 min read

Unreal Tournament 2004 came out twenty-two years ago. It shipped with 100+ maps, a map editor, full mod support, dedicated server binaries for every platform, and an in-game server browser that worked. The movement — dodge-jumping, wall-dodging, lift-jumping, shield-gunning across entire maps — has never been matched. Not by Quake Champions, not by Diabotical, not by any of the arena shooter revivals that launch to 200 concurrent players and die within six months.

UT2004 didn't die. Epic killed it. They shut down the master servers, stopped selling it, and moved on to Fortnite. But the community kept playing. People ran their own master server lists. Clans kept their servers online. Discord channels replaced IRC. And then, quietly, Epic restored the official master servers and made the game free.

The game is back. What it's missing is a modern competitive layer. That's what UT2K4EVER is.

UT2004 DM-Rankin gameplay

What we're building

UT2K4EVER is a competitive matchmaking platform for UT2004. You create an account, join a queue, get matched with players at your skill level, and a fresh dedicated server spins up for your match. When the match ends, stats are recorded, Elo ratings adjust, and you queue again.

It's not a mod. It's not a mutator pack you install. It's a standalone platform — a web app that orchestrates everything. The UT2004 game client is just the renderer. All the competitive logic lives server-side.

The goal is simple: make it as easy to play competitive UT2004 as it is to play ranked in any modern game. No hunting for servers. No waiting in half-empty lobbies. No clan wars that require scheduling on forums. Just queue and play.

The stack

The backend is a single Go binary. No microservices, no message queues, no Redis. One process handles authentication, matchmaking, real-time chat, and UT2004 server management. The database is SQLite — fast, zero-config, and trivially backed up.

Component Technology
Backend Go, Chi router, SQLite
Frontend React, Vite, TanStack Router
Game integration UnrealScript mutator, custom TCP protocol
State management Zustand
Deployment Docker, Caddy

The frontend is a React SPA styled after mIRC and NNscript. If you were in the UT2004 competitive scene in 2004, you were in IRC. You were running scripts that formatted your stats, announced matches, and auto-responded to challenges. The UI leans into that nostalgia — dark backgrounds, bright monospace text, channel-window-style panels, system messages that feel like IRC notices.

UT2K4EVER lobby interface

How matchmaking works

The matchmaking system is the core of the platform. It supports multiple game modes — 1v1 Duel, 2v2 TDM, 4v4 TDM, and CTF — each with its own queue and rating pool.

When you click "Queue," you enter a skill-bracketed pool. The matcher runs on a tick, looking for groups of players whose Elo ratings are close enough to produce a fair match. The acceptable Elo range widens over time — if you've been in queue for 30 seconds, we'll match you against slightly wider skill brackets rather than making you wait five minutes for a perfect match.

Once enough players are found, the system creates a match proposal. Every player in the proposed match gets a notification with a 30-second accept timer. If everyone accepts, the match is confirmed. If anyone declines or times out, they're removed from the queue and everyone else is re-queued immediately.

This proposal system is critical. It prevents AFK players from ruining matches and ensures that when a match starts, every player is actively at their keyboard.

Server spawning

When a match is confirmed, the backend spawns a new UT2004 dedicated server process. This isn't a shared server that runs 24/7 — it's a fresh instance created specifically for this match, with:

  • The correct game mode and player count configured
  • A map selected from the competitive pool
  • A custom mutator loaded that connects back to the Go backend via TCP
  • A unique join password that only the matched players receive

The mutator is the bridge between UT2004 and the platform. It's written in UnrealScript — the embedded scripting language that UT2004 uses for all game logic. When loaded, the mutator:

  1. Opens a TCP connection to the Go backend
  2. Reports player joins, team assignments, kills, deaths, and weapon stats
  3. Enforces team assignments (no switching sides mid-match)
  4. Tracks weapon accuracy through a UTComp fork we maintain
  5. Reports the final score and triggers server shutdown

The entire lifecycle — from queue to match completion — is managed programmatically. No admin intervention required.

UT2004 match in progress — DM-1on1-Irondust

Warmup servers

One of the biggest problems with matchmaking in small communities is queue times. If there are only 30 people online and you need 8 for a 4v4, you might wait a while. Staring at a "Searching for match…" screen is the fastest way to kill a platform.

Our solution: warmup servers. While you're in queue, you can instantly join a free-for-all deathmatch server. You're fragging, practicing aim, warming up your movement — and the moment your match is found, you get pulled out of the warmup server and into your competitive match.

The warmup servers run continuously and auto-scale. When they're getting crowded, the system spawns another one. When they're empty, they shut down. Players in warmup servers are still in the matchmaking queue — the system tracks their state across both contexts.

This means the queue never feels like a waiting room. You're always playing.

The Elo system

We use a modified Elo rating system. Every player starts at 1500. After each match, ratings adjust based on the outcome and the relative skill of the teams.

The key parameters:

  • K-factor: 40 for your first 10 matches (placement), 20 after that. This means new players' ratings move quickly to their actual skill level, while established players' ratings are more stable.
  • Team averaging: In team modes, the expected outcome is calculated from each team's average Elo. Individual ratings adjust based on the team result.
  • Win/loss only: We deliberately don't factor in individual performance (kills, deaths, damage) into rating changes. This prevents stat-padding behavior and keeps the focus on winning the match as a team.

Ratings are visible on your profile, on the leaderboard, and in the matchmaking UI. When you're in queue, you can see approximately what rating range you'll be matched against.

The UnrealScript mutator

The mutator is the most unusual part of the stack. UnrealScript is a domain-specific language that runs inside the Unreal Engine — it's what UT2004's game logic is written in. It looks like a cross between Java and Delphi, it's 22 years old, and the documentation was last updated in 2004.

We maintain two UnrealScript packages:

UT2K4Ever — the main mutator. Four classes:

  • UT2K4EverMutator: Entry point, manages the TCP connection lifecycle
  • UT2K4EverTCPClient: Handles the wire protocol (line-based JSON over TCP)
  • UT2K4EverTeamEnforcer: Prevents players from switching teams during competitive matches
  • UT2K4EverPlayerTracker: Monitors player joins, leaves, and team assignments

UT2K4ECompv18c — our fork of UTComp, the beloved competitive mod that the UT2004 community relied on for years. UTComp adds hit sounds, weapon stats tracking, and accuracy overlays. Our fork strips out the features that conflict with the matchmaking system (like its own stat storage) and adds a data pipeline that sends weapon accuracy stats back to the Go backend.

The compilation pipeline is managed through a Makefile that syncs source files to the UT2004 server directory, invokes the UCC compiler (UT2004's build tool), compresses packages for HTTP download, and deploys them.

UT2004 CTF-FaceClassic — the most iconic map

Chat and social features

Real-time chat is built into the platform. It's modeled after IRC — you have channels, private messages, and system notifications. When you're in a match, you automatically join the match channel. When you're in queue, you're in the global lobby.

Chat is persistent. Messages are stored in SQLite so you don't lose context when you refresh the page. The real-time transport is WebSocket — every connected client maintains a WebSocket connection to the backend, which is used for chat, matchmaking notifications, presence updates, and queue status.

The friends system tracks who's online, what they're doing (in queue, in match, idle), and lets you invite people to queue together as a stack — a pre-formed team that enters matchmaking as a unit.

Architecture decisions

Why a monolith? Because there are maybe 200 competitive UT2004 players in the world. A monolith running on a single server in Helsinki handles all of them with headroom to spare. The code is simpler, the deployment is simpler, and when something breaks at 2am, there's one log to read.

Why SQLite? Same reason. One process, one database file, zero network hops. SQLite handles concurrent reads excellently and write contention isn't an issue at our scale. Backups are literally "copy this one file."

Why Go? It's the right language for a server that manages long-lived connections (WebSockets), spawns child processes (UT2004 servers), and needs to be a single static binary that you deploy with scp. Go's concurrency model (goroutines, channels) maps perfectly to the problem.

Why not just use a Discord bot? Because we want a proper competitive experience. Discord bots can do matchmaking, but they can't spawn game servers, enforce team assignments, track weapon stats, calculate Elo, or provide a purpose-built UI. The UX of "react with an emoji to join the queue" doesn't cut it.

The road to launch

We're launching in three milestones:

March 20 — Closed Beta. Invite-only, roughly 20 players. The goal is to validate the core loop: queue → match → play → stats. We'll be fixing bugs in real-time and gathering feedback.

March 27 — Community Servers. We open the platform for community members to contribute game server capacity. If you have a box with UT2004 installed, you can connect it to the platform and it becomes available for matchmaking. This is how we'll scale to multiple geographic regions without paying for servers everywhere.

April 2 — Public Launch. Easter Sunday. Registration opens to everyone. Servers in Europe and North America. The full platform goes live: matchmaking, leaderboards, chat, stats, everything.

UT2004 DM-Deck17 — aerial view

Why now

UT2004 is free. The master servers are back. The game runs on anything — it was designed for hardware that doesn't exist anymore, so even a laptop from 2015 runs it at 200 FPS. The community has been maintaining servers, skins, and maps for two decades.

What's been missing is a reason to come back. Not a nostalgia trip where you play three deathmatch rounds and uninstall. A persistent competitive ecosystem where your rating matters, your stats are tracked, and there's always someone to play against at your skill level.

Arena shooters didn't fail because people stopped wanting them. They failed because every new one expected you to start from scratch — new maps, new weapons, new movement systems — and then the playerbase evaporated in three months. UT2004 has 22 years of maps, 22 years of muscle memory, and a movement system that's been optimized by thousands of players. We're not trying to replace that. We're building the infrastructure around it.

The game is free. The platform is free. Come frag.