Quake II re-release port to TypeScript with WebGL renderer - A complete game engine with physics, networking, and BSP rendering
npm install quake2ts> A TypeScript/WebGL port of the Quake II re-release engine for creating interactive web-based game engine visualizations and experiments.


quake2ts is a modular, browser-first implementation of the Quake II engine in TypeScript with WebGL2 rendering. It provides a complete game engine architecture broken into composable packages, making it ideal for:
- ๐ฎ Interactive engine visualizations - Build web apps that explore game engine internals
- ๐ฌ Educational projects - Learn game engine architecture through a well-structured codebase
- ๐ ๏ธ Asset viewers - Create BSP map viewers, MD2 model viewers, or PAK file explorers
- ๐งช Physics experiments - Test and visualize player movement, collision detection, and game mechanics
- ๐จ Retro game development - Build new games using classic Quake II technology
The library is organized as a pnpm monorepo with independent packages for different engine layers, allowing you to use only what you need.
``bash`
npm install quake2tsor
pnpm add quake2tsor
yarn add quake2ts
`html`
`typescript
import { bootstrapViewer } from '@quake2ts/viewer';
// Bootstrap the complete engine with game logic and rendering
const { engine, game, client, runtime } = bootstrapViewer();
// Runtime automatically starts at 40Hz simulation tick
`
For interactive engine exploration, import individual packages:
`typescript
import { createEngineRuntime } from '@quake2ts/engine';
import { createGame } from '@quake2ts/game';
import { createClient } from '@quake2ts/client';
import { ingestPakFiles, wireFileInput } from '@quake2ts/engine';
// 1. Set up asset loading with progress tracking
const input = document.getElementById('pak-input') as HTMLInputElement;
wireFileInput(input, async (pakSources) => {
await ingestPakFiles(pakSources, (progress) => {
console.log(Loading assets: ${progress.percent}%);
});
// 2. Create engine, game, and client
const engine = createEngine({ / trace callback / });
const game = createGame({ / trace callback / });
const client = createClient({ engine });
// 3. Bootstrap runtime
const runtime = createEngineRuntime(engine, game, client);
runtime.start();
});
`
quake2ts is organized into 5 core packages and a viewer app:
Shared math, physics, and protocol utilities.
Key Exports:
- Math utilities: Vec3 operations, angle conversions, color blending, random number generationCONTENTS_
- Collision detection: , SURF_, MASK_* constantspmove
- Player movement physics: Complete implementation with friction, acceleration, jumping, water/air movement, ducking, and stuck detection
`typescript
import { Vec3, pmove, MASK_PLAYERSOLID } from '@quake2ts/shared';
// Use Quake II's player movement physics
const result = pmove(playerState, userCmd, traceFunction);
console.log('New position:', result.origin);
console.log('New velocity:', result.velocity);
`
Web-facing services: WebGL2 rendering, asset loading, virtual filesystem, and engine runtime.
Key Exports:
Asset System:
- PakArchive, VirtualFileSystem - PAK file reading and virtual filesystemingestPaks
- , ingestPakFiles - Asset ingestion pipeline with progress callbackswireFileInput
- , wireDropTarget - Browser file handling helpersBspLoader
- , parseBsp - BSP map parsingMd2Loader
- , parseMd2 - MD2 model parsing with animation supportLruCache
- - Asset caching system
Rendering:
- createWebGLContext - WebGL2 context initializationShaderProgram
- , VertexBuffer, IndexBuffer, Texture2D - GPU resource wrapperscreateBspSurfaces
- - Converts parsed BSP map data into renderable surfacesbuildBspGeometry
- - BSP geometry builder with lightmap atlas packing
Engine Core:
- FixedTimestepLoop - Deterministic 40Hz simulation loop with interpolationEngineHost
- - Game/client lifecycle managerEngineRuntime
- , createEngineRuntime - Complete runtime bootstrapCvarRegistry
- , Cvar - Configuration variable systemConfigStringRegistry
- - Deterministic asset indexing
`typescript
import {
ingestPakFiles,
buildBspGeometry,
createWebGLContext,
FixedTimestepLoop
} from '@quake2ts/engine';
// Create a BSP map viewer
const vfs = await ingestPakFiles(pakSources);
const bspData = vfs.readFile('maps/base1.bsp');
const geometry = buildBspGeometry(bspData);
// Set up WebGL rendering
const gl = createWebGLContext(canvas);
const loop = new FixedTimestepLoop(
(dt) => { / simulate / },
(alpha) => { / render with interpolation / }
);
loop.start();
`
Authoritative simulation and entity system.
Key Exports:
- createGame - Main game factory functionEntitySystem
- - Entity management with poolingEntity
- - Base entity class with MoveType, Solid, think callbacks, and field metadataGameFrameLoop
- - Frame timing with prep/simulate/post stagesLevelClock
- - Level time tracking
`typescript
import { createGame, Entity, MoveType, Solid } from '@quake2ts/game';
const game = createGame(
{ trace: / collision trace / },
{ gravity: { x: 0, y: 0, z: -800 } }
);
// Create custom entities
const platform = new Entity();
platform.moveType = MoveType.Push;
platform.solid = Solid.Bsp;
platform.think = () => {
// Update logic
};
game.entitySystem.spawn(platform);
`
Client-side rendering interface and state prediction.
Key Exports:
- createClient - Client factory with prediction supportClientRenderer
- - Rendering interfacePredictionState
- - Client-side state prediction
`typescript
import { createClient } from '@quake2ts/client';
const client = createClient({ engine });
// Client handles HUD rendering and prediction
`
Utilities for asset preparation and inspection.
Key Exports:
- describeAsset - Asset summary and metadata extraction
`typescript
import { describeAsset } from '@quake2ts/tools';
const info = describeAsset(assetBuffer);
console.log(info);
`
Minimal viewer harness demonstrating complete engine integration.
Key Exports:
- bootstrapViewer - Complete engine/game/client bootstrap
`typescript
import { ingestPakFiles, buildBspGeometry, VirtualFileSystem } from '@quake2ts/engine';
// Load PAK files
const vfs = await ingestPakFiles(pakSources, (progress) => {
updateProgressBar(progress.percent);
});
// List all maps
const maps = vfs.listFiles('maps/*.bsp');
console.log('Available maps:', maps);
// Render a specific map
const bspBuffer = await vfs.readFile('maps/base1.bsp');
const bspMap = parseBsp(bspBuffer);
const surfaces = createBspSurfaces(bspMap);
const geometry = buildBspGeometry(gl, surfaces);
// Display geometry stats
console.log(Surfaces: ${geometry.surfaces.length});Lightmaps: ${geometry.lightmaps.length}
console.log();
// Render with WebGL
// (Use BspSurfacePipeline or your own shader to render geometry.surfaces)`
`typescript
import { Md2Loader, parseMd2 } from '@quake2ts/engine';
// Load MD2 model
const modelData = vfs.readFile('models/monsters/soldier/tris.md2');
const model = parseMd2(modelData);
// Display model info
console.log(Frames: ${model.header.numFrames});Animation groups:
console.log(, model.animations);
// Animate between frames
let currentFrame = 0;
const animationGroup = model.animations.find(a => a.name === 'run');
function animate() {
currentFrame = (currentFrame + 1) % animationGroup.frameCount;
const frame = model.frames[animationGroup.firstFrame + currentFrame];
renderMd2Frame(gl, frame, model.triangles);
requestAnimationFrame(animate);
}
animate();
`
`typescript
import { pmove, Vec3 } from '@quake2ts/shared';
import { createGame } from '@quake2ts/game';
// Set up player state
const playerState = {
origin: { x: 0, y: 0, z: 24 },
velocity: { x: 0, y: 0, z: 0 },
// ... other pmove fields
};
// Simulate user input
const userCmd = {
forwardmove: 400, // Move forward
sidemove: 0,
upmove: 0,
buttons: 0,
angles: { pitch: 0, yaw: 90, roll: 0 }
};
// Run one physics tick
const result = pmove(playerState, userCmd, traceFunction);
// Visualize the movement
drawPlayerPosition(result.origin);
drawVelocityVector(result.origin, result.velocity);
console.log(Speed: ${Vec3.length(result.velocity)} units/sec);`
`typescript
import { createGame, MoveType, Solid } from '@quake2ts/game';
const game = createGame({ trace }, { gravity: { x: 0, y: 0, z: -800 } });
// Spawn various entity types
const entities = [
{ type: 'static', moveType: MoveType.None, solid: Solid.Bsp },
{ type: 'physics', moveType: MoveType.Toss, solid: Solid.BoundingBox },
{ type: 'player', moveType: MoveType.Walk, solid: Solid.BoundingBox },
{ type: 'trigger', moveType: MoveType.None, solid: Solid.Trigger }
];
entities.forEach(config => {
const entity = new Entity();
entity.moveType = config.moveType;
entity.solid = config.solid;
entity.think = () => {
// Visualize entity state in real-time
highlightEntity(entity);
};
game.entitySystem.spawn(entity);
});
// Run simulation and visualize
game.loop.start();
`
Each package is published in three formats:
- ESM - dist/esm/index.js - Modern ES modules (recommended)dist/cjs/index.cjs
- CJS - - CommonJS for Node.jsdist/browser/index.js
- Browser - - Minified IIFE for