quake-rb
A port of the Quake (1996) engine to Ruby. Reads original .pak/.bsp/.mdl assets and renders them via OpenGL + SDL2.
Why
Mostly as a counterpart to doom - to see how far you can take id Software's classic engines in plain Ruby, and to learn the BSP/PVS/lightmap pipeline.
Quick start
gem install quake-rb
quakeOr fetch the repository and run.
bundle install
bundle exec bin/quakeOn first run, with no data/id1/pak0.pak present, you'll be asked to download the freely-redistributable shareware Quake (Episode 1, ~17 MB). It's pulled from archive.org/quakeshareware/QUAKE_SW.zip and extracted into ./data/id1/pak0.pak. You can also drop your own registered pak0.pak (and pak1.pak) in there manually.
System requirements:
- Ruby 3.1+
- SDL2 (
brew install sdl2/apt install libsdl2-dev) - OpenGL 2.1
-
unzip(used by the shareware fetcher)
Controls
| Key | Action |
|---|---|
| WASD | Move |
| Mouse | Look |
| Space | Jump |
| C | Crouch |
| 1–8 / scroll | Weapon select |
| N | Toggle noclip |
| L | Dump player pose + save a screenshot to debug/shots/ (debug aid) |
| Esc | Quit |
What works
- BSP loading + face extraction with texture coordinates
- Texture mapping with the original 256-colour palette
- Static lightmaps from the lighting lump
- PVS culling, including:
- leaf 0 fallback (treat solid leaf as all-visible, à la TyrQuake)
- FatPVS expansion when the camera leaf touches a liquid surface
- vis-through-water: liquid faces in PVS bring the leaves on the other side of their plane into view, so translucent water shows the cave bed instead of clear-color void
- Sky rendering: two-layer scrolling cloud + back layer, PVS-culled
- Water/slime/lava: turbsin-warped texture, alpha-blended translucency, backface culling for the duplicate front/back face pairs Quake's BSP compiler emits
- Brush entities (doors, plats, buttons, triggers) with their own transforms
- MDL alias models with frame interpolation
- Particle effects + viewmodel + HUD
- Player physics: walk, crouch, jump, friction, water levels, platform riding
- Item pickups, weapon switching, Quake-style player weapon firing, shareware audio mixer
- Common QuakeC trigger behavior at engine level, including target firing, killtarget, counters, hurt/push/teleport triggers, and changelevel requests
-
A real QuakeC VM: the original
progs.datfrompak0.pakis loaded and interpreted, with the server side of Quake around it (edicts,SV_Physics, think/touch dispatch, world collision, monster movement, the builtin table). Shareware maps spawn through their real QuakeC spawn functions in the headless runner, and monsters see, chase and shoot from id's bytecode. See docs/quakec-vm.md.
What doesn't (yet)
- Multiplayer / netcode. The messaging builtins (
sound,particle,lightstyle, theWrite*family) reach the renderer and mixer directly instead of a client connection - Savegames
- Mirrors, particle warp, dynamic lights
- Sky cube / skybox replacement
- Demo playback
Maps
The shareware PAK ships:
start, e1m1, e1m2, e1m3, e1m4, e1m5, e1m6, e1m7, e1m8
Pass -map maps/e1m3.bsp to load a different one.
Architecture
lib/quake/
bsp/ reader, types, face vertices, PVS / FatPVS / vis-through-water
pak/ pak0.pak reader
mdl/ alias model reader
wad/ gfx.wad (HUD/menu graphics) reader
palette.rb 256-colour palette → RGBA
math/vec3.rb plain Ruby vector
physics/ hull trace + Player (walk/swim/jump/noclip)
progs/ the QuakeC VM
program.rb progs.dat image: statements, defs, functions, strings, globals
edicts.rb edict field memory, ED_Alloc / ED_Free
vm.rb PR_ExecuteProgram -- the 66-opcode interpreter
builtins.rb the NetQuake builtin table (#1 makevectors .. #78 setspawnparms)
server/ the QuakeC server
world.rb areanodes, SV_LinkEdict, SV_TouchLinks, SV_Move
physics.rb SV_Physics, the MOVETYPE handlers, SV_RunThink, SV_Impact
monster_move.rb SV_movestep, SV_NewChaseDir, SV_CheckBottom
client_move.rb SV_ClientThink -- player input to client-edict velocity
server.rb SV_SpawnServer, ED_LoadFromFile, clients, the builtin host
services.rb boundary to this engine's sound / particles / console
game/
engine.rb orchestrates renderers + physics + entity simulation
brush_entities entity-driven door/plat/button state machines
item_pickups touchable items
player_state weapons, ammo, health, armor
renderer/
gl_textured opaque world brushes (with lightmap pass)
gl_brush_model sub-model brushes (doors etc.) at entity transforms
gl_alias_model .mdl monsters + items
gl_water *water0/*slime0/*lava0/*teleport with turbsin warp
gl_sky two-layer scrolling sky
gl_lightmap lightmap atlas builder
gl_texture_manager mipmapped diffuse texture cache
gl_particles explosion/spark sprites
gl_viewmodel first-person weapon
gl_hud statusbar + numbers
sound/ SDL_mixer-backed sound effects
pak_downloader.rb shareware fetcher
version.rb VERSION = "0.1.0"
bin/
quake interactive game
quake-debug headless DSL runner (`teleport`, `screenshot`, `dump` etc.)
quake-progs headless QuakeC server runner (no window, renderer or sound)
test/ minitest suite
debug/scripts/ ad-hoc investigation scripts (gitignored)
The headless runner is genuinely useful for working on the engine without a window — see debug/scripts/* for examples (water visibility traces, leaf inspection, BSP dumps).
Development
# Run the tests
bundle exec ruby -Itest -Ilib -e 'Dir["test/**/*_test.rb"].each { |f| require File.expand_path(f) }'
# Run a headless debug script (renders to a hidden window)
bundle exec bin/quake-debug debug/scripts/water_real.rb
# Run the game with YJIT
bundle exec bin/quake --yjit
# Spawn a map through the real progs.dat and step the server, headless
bundle exec ruby -Ilib bin/quake-progs e1m1 --frames 300
# List spawned monsters after the headless run
bundle exec ruby -Ilib bin/quake-progs e1m1 --frames 300 --list monster_
# Play with the QuakeC VM driving the game instead of the hand-ported logic
bundle exec bin/quake --progsStatus
This is a hobby project. It can comfortably walk you through e1m1, fire Quake-style player weapons, pick up items, and use a growing subset of QuakeC brush/trigger behavior.
The QuakeC VM now runs the real progs.dat, which is the interesting half: monsters
spawn, patrol, notice you and shoot back, all from id's own bytecode rather than a
reimplementation. The interactive engine still defaults to the older hand-ported game
logic; VM-driven play is available behind bin/quake --progs and is still being wired in.
License
GPL-2.0-only, matching the original Quake source release.
The shareware Quake data is © 1996 id Software, freely redistributable. The registered Quake data (if you supply your own pak0.pak + pak1.pak) is not redistributable; you must own a copy.
