LuisAldrichGuz

THREE ARENA

Stack: Three.js, TypeScript, Node.js, Vite

Demo

THREE ARENA

An arena shooter engine that runs in the browser and plays the real data files of Quake 3 Arena and OpenArena. The ZIP container, the BSP level format, the Bezier patches, the baked lightmaps and the player physics are all written from scratch in TypeScript and three.js. No engine, no asset importer, and no WebAssembly build of the original.

I'm not after anything with this. It's a homage to one of the games that inspired me, and to John Carmack, who is the one I look up to: the best there is.

I work on it in my spare time. GPL-2.0-or-later.

The Three Arena main menu
54 arenas and three bots, read straight out of 1999's own data
Dropping into a match
Spawning, meeting the first one and grabbing whatever is there

What is actually in here

Two halves that meet at a file format.

A pipeline that opens a .pk3 —a ZIP full of 1999 game data— and writes a .glb of the level with its original baked lighting, plus a JSON of its collision hull. It reads the ZIP central directory, the seventeen BSP lumps, the Targa textures, the engine's own .shader scripts and the lightmaps, and it tessellates the Bezier patches that every arch and curved ramp in a Quake level is made of.

And an engine that loads those two files and plays them.

Converting a map takes well under a second. wrackdm17 comes out at 37,475 triangles and 2.09 MB, with its 56 patches, its 1,002 brushes and all eight lightmaps packed into a single atlas page.

The orbital viewer with the conversion stats
The orbital viewer: what each map cost, triangle by triangle
The converted map, being played
And the same file, now being played: that light was baked in 1999

The physics do not resemble Quake's. They are Quake's.

Ported function by function from bg_pmove.c. Running measures 319 units per second against the original's 320, and three chained strafe jumps reach 465.

Strafe jumping works because the acceleration code has the same exploitable behaviour the 1999 one had. I did not imitate the technique: I ported the maths, and the trick came inside.

The movement test arena
Staircase, ramp, slab and corner, with no game data involved at all

Twenty-four tests assert the exact numbers the original engine produces: the ground speed, what three chained jumps gain, 16 unit stairs climbed and 26 unit ledges not, and corners that neither trap the player nor let them through.

It plays

::carousel images=[{"src": "/media/project-list/images/three-arena/arenas.webp", "alt": "Arena picker", "caption": "The converted arenas, and the bots you drop into them"}, {"src": "/media/project-list/images/three-arena/setup.webp", "alt": "Character select", "caption": "OpenArena's own models, loaded from their own .md3 files"}, {"src": "/media/project-list/images/three-arena/combate.webp", "alt": "Combat", "caption": "A bot across the map and the FIGHT callout"}, {"src": "/media/project-list/images/three-arena/marcador.webp", "alt": "In-game scoreboard", "caption": "The scoreboard without leaving the match"}, {"src": "/media/project-list/images/three-arena/bots.webp", "alt": "Two bots fighting", "caption": "The bots kill each other and the scoreboard notices"}, {"src": "/media/project-list/images/three-arena/municion.webp", "alt": "Ammo on the floor", "caption": "Map items come out of the BSP; nothing is placed by hand"}] height="460px"::

Frags in a row
The bots do not wait their turn

Playing with someone else is one more command

By default it is one player against bots, and it has to be: the point is that it opens from a static file host with nothing behind it. The server is optional. You start it, it prints a link, and whoever opens it joins your match — predicted on your side, interpolated on theirs, and if you close the tab and come back you return to the same seat with your score.

Weapons and audio do not cross the wire yet. Only movement, reconnection and the scoreboard do.

The game data is not in the repository, and that is deliberate

A converted map is a derivative work of the map it came from. Shipping one here would mean redistributing OpenArena's art under my name, so the .gitignore throws out .pk3, .bsp and .glb files, and the repository carries none.

Whoever runs it supplies the data once: 426 MB of OpenArena 0.8.8, which is free software. The pipeline points at it and that is that.

The code is GPL-2.0-or-later for the same reason: so the conversion you make on your own machine is yours with no asterisks.

Status

It plays. I'm putting the offline mode up —one player against the bots—, which is the one that is already there. The online side is what I'm working on: what's left is weapons and audio crossing the network.

The end of a match
The last frag and the scoreboard

It plays here: arena.luisaldrichguz.net. Desktop only, and it wants your mouse.