Project

quake-rb

0.0
The project is in a healthy, maintained state
A port of the Quake (1996) engine to Ruby. Reads original BSP/MDL/PAK assets and renders them via OpenGL + SDL2.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Development

~> 5.0
~> 13.0

Runtime

 Project Readme

quake-rb

A port of the Quake (1996) engine to Ruby. Reads original .pak/.bsp/.mdl assets and renders them via OpenGL + SDL2.

quake-rb running e1m1

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
quake

Or fetch the repository and run.

bundle install
bundle exec bin/quake

On 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.dat from pak0.pak is 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, the Write* 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 --progs

Status

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.