Get AI summaries of any video or article — Sign up free
Quake In 13kb Of Javascript thumbnail

Quake In 13kb Of Javascript

The PrimeTime·
6 min read

Based on The PrimeTime's video on YouTube. If you like this content, support the original creators by watching, liking and subscribing to their content.

TL;DR

Procedural texture generation replaces external PNG assets to avoid blowing the zipped size budget (31 textures drop from ~150KB PNGs to ~1.3KB zipped definitions).

Briefing

A 13KB JavaScript build of Quake (“q1 K3”) pulls off a full FPS experience—textures, sounds, music, weapons, enemies, and two classic-style maps—by generating most assets in code and aggressively compressing both data and logic. The core takeaway isn’t just nostalgia for the 1996 original; it’s a set of practical size-golf techniques that make a complex 3D game feasible inside the JS13k constraint, where the final submission must fit in a 13KB zipped package.

Instead of shipping pre-made art, the project generates its 31 textures on the fly using a tiny canvas-based texture DSL. Early attempts relied on external PNG textures, but that immediately blew the budget (about 150KB just for textures). The solution was to build a miniature “texture library” with only a handful of drawing primitives—embossed rectangles and grids, noise, and text—then compose those primitives to create grunge-like materials for grass, sky, water, and more. A custom texture editor lets the author draw one texture onto another (including onto itself), producing a full texture set whose definitions zip to roughly 1.3KB.

Map data follows a similar “store less, compute smarter” philosophy. The famous E1M1 layout is represented using axis-aligned bounding blocks (AABBs). That choice sacrifices slopes and fine detail—making the world look more like Minecraft than original Quake—but it slashes geometry storage and simplifies collision math. Collision then becomes tractable through a bitmapped occupancy grid (128×128 cells), so collision checks reduce to looking up which grid cells an entity’s bounding box overlaps, then resolving movement direction-by-direction to allow sliding along walls and floors.

Models and animations are also compressed with Quake-like reuse. Only a few animated model types exist (a basic humanoid, a dog, and a torch), and enemies are created by scaling/stretching the same base geometry and swapping textures. Animated frames are stored compactly, and vertex positions use bit-level packing to improve zip behavior. Geometry is further trimmed by removing unseen faces; even the Quake logo on the title screen is optimized by stripping most sides.

Audio and music are handled with size-first tooling. Music leans on a heavily modified tracker (Sonitent X / ATT-style approach), with oscillator lookup tables generated at startup and music stored in flat arrays rather than JSON objects. Sound effects use a simple spatial audio model: distance and angle to the camera drive volume and stereo panning. The result is a moody, minimalist soundtrack that compresses well, plus spatialized effects that still fit the budget.

All assets and data are packed to leave room for the engine. Two levels total 563 blocks and 188 entities, with level data around 4.5KB uncompressed (3.2KB zipped). Models and textures together land in the low single-digit kilobytes, and the entire game—including unpacking, rendering, physics, AI hooks, and gameplay logic—fits by using minification, zip-friendly data ordering, and an extra compression step via a JavaScript packer (“Road Roller”) that effectively frees about 1.2KB.

The broader message is that “game development” inside JS13k becomes less about authoring everything by hand and more about generating data, choosing representations that compress well, and building tiny tools that provide fast feedback. The payoff is a surprisingly faithful Quake-like experience delivered under extreme constraints—proving that careful engineering can beat the size limit without turning the game into a toy.

Cornell Notes

q1 K3 recreates a Quake-like FPS under the JS13k rule by generating assets in code and compressing data aggressively. Textures are produced with a tiny canvas-based DSL (noise, embossed rectangles/grids, and text) and composed via a small editor; the full 31-texture set is about 1.3KB zipped instead of ~150KB of PNGs. Levels are stored as axis-aligned bounding blocks (AABBs) to simplify collision and reduce geometry, then collision uses a bitmapped 128×128 occupancy grid. Models are minimized through reuse (few animated base types, scaled variants for enemies) and bit-packed animation/vertex data to improve zip compression. Music and effects are also size-golfed using a modified tracker with lookup tables and flat arrays, plus a simple spatial audio model.

Why does generating textures in code matter so much for a 13KB zipped limit?

External textures quickly exceed the budget: the project notes that 31 PNG textures were about 150KB, which is impossible to include in a 13KB zipped submission. The workaround is a texture-generation library that draws materials procedurally on a canvas using a small set of primitives (embossed rectangles/grids, noise, and text). A custom editor composes textures by drawing one texture onto another (even onto itself). The resulting texture definitions for all 31 textures zip to roughly 1.3KB, leaving room for the engine and other assets.

What tradeoff does the project make by using axis-aligned bounding blocks (AABBs) for maps?

AABBs make collision and storage far simpler: geometry is reduced to blocks aligned to the grid, and collision becomes straightforward because math stays axis-aligned. The cost is visual fidelity—missing slopes and fine details make the world look more like Minecraft than original Quake. The author frames this as a necessary sacrifice to hit the size goal while keeping gameplay functional.

How does collision detection become feasible without storing complex geometry?

Instead of testing against detailed mesh geometry, the engine uses a bit map for a 128×128 grid. Each bit indicates whether a cell is occupied. When an entity moves, its bounding box is checked against the relevant grid cells; movement is resolved in direction steps so entities can slide along floors and walls rather than stopping completely. This approach avoids heavy per-triangle collision while staying compact.

How are models kept small while still supporting multiple enemy types and animations?

The project relies on reuse and transformation. Only a few animated model types are used (humanoid with six frames, dog with two frames, torch with three frames). Many enemy types are created by stretching/scaling the same base humanoid geometry and applying different textures (e.g., ogre vs enforcer). Geometry is also trimmed by removing faces that won’t be seen (e.g., the Quake logo title model has most sides removed). Vertex/animation data is packed to improve zip compression.

What size-golf techniques make the music and sound fit?

Music is produced with a tracker workflow but stored and generated in a zip-friendly way: oscillator lookup tables (sin/square/sawtooth/triangle) are generated at startup so runtime generation is cheap, and music data is stored in flat arrays rather than JSON objects. Sound effects use a simple spatial model: compute distance and angle from the camera to each sound-emitting entity, then adjust volume and stereo panning through a small Web Audio graph.

How does extra compression beyond minification help meet the JS13k package limit?

Minification alone still leaves too little room once maps, textures, models, and audio are included. The project uses Road Roller, a JavaScript packer that compresses the source and appends decompression code. This provides about 1.2KB of extra space “for free,” enabling the final build to fit the 13KB zipped requirement without changing the core source logic.

Review Questions

  1. Which representation choices (textures, maps, collisions, models) most directly improve zip compression, and why?
  2. How do AABBs and the 128×128 occupancy bit map work together to keep collision logic both small and playable?
  3. What procedural generation primitives and composition steps are used to build the texture set, and how does that compare to shipping PNG assets?

Key Points

  1. 1

    Procedural texture generation replaces external PNG assets to avoid blowing the zipped size budget (31 textures drop from ~150KB PNGs to ~1.3KB zipped definitions).

  2. 2

    A tiny texture DSL plus a texture editor (noise, embossed rectangles/grids, text, and compositing) is enough to create a full material set under extreme constraints.

  3. 3

    Map geometry is reduced to axis-aligned bounding blocks (AABBs), trading away slopes and fine detail to simplify storage and collision math.

  4. 4

    Collision uses a compact 128×128 occupancy bit map, so entity-vs-world checks become grid lookups plus direction-by-direction sliding resolution.

  5. 5

    Models are minimized through reuse (few animated base types) and transformations (scaling/stretching) rather than unique geometry per enemy.

  6. 6

    Music is size-golfed by using oscillator lookup tables and storing tracker output in flat arrays instead of JSON objects.

  7. 7

    When minification isn’t enough, a JS packer (Road Roller) can free ~1.2KB by compressing source and adding decompression code at runtime.

Highlights

The texture pipeline is the budget win: 31 PNGs at ~150KB are replaced by code-generated textures whose definitions zip to about 1.3KB.
Levels use AABBs and a 128×128 occupancy bit map, turning collision into compact grid checks instead of complex geometry tests.
Enemy variety comes mostly from reusing a small set of animated models and transforming them (scaling/stretching) with different textures.
Music generation is optimized for size and speed by precomputing oscillator lookup tables and storing music in flat arrays.
Road Roller provides extra headroom (~1.2KB) by packing the JavaScript and appending decompression logic, helping the build fit the 13KB zipped rule.

Topics

  • JS13k
  • Procedural Textures
  • AABB Collision
  • Data Compression
  • Web Audio
  • Quake Port

Mentioned

  • John Carmack
  • Neil Graham
  • Andy L
  • Brandon Jones
  • Ardan Cobra
  • Grandpa Mitch
  • D fizzy
  • Taco Bon
  • Coding atsy
  • Coding at the speed of light
  • AABBs
  • DSL
  • JS13k
  • Oculus Rift
  • FFM Peg
  • ATT
  • GPU
  • WebGL
  • RLE
  • Huffman
  • BSP
  • TCP
  • GC
  • SVG
  • SVG paths
  • UTF 16