Catlantean 3D: Engineering a 1993-Style Software Rasterizer
Catlantean 3D is a first-person shooter developed with a strict set of self-imposed technical constraints designed to emulate the graphics and performance characteristics of early 1990s PC gaming. The project focuses on building a complete, shippable game from scratch—including the engine, assets, and sound mixing—without relying on modern GPU acceleration or AI-generated content.
Technical Constraints and Rendering Architecture
Catlantean 3D utilizes a software rasterizer targeting a 320x240 resolution with a 256-color palette. This architecture mimics the simplicity of VGA Mode 13h (320x200), but adjusts the vertical resolution to 240 to ensure square pixels on modern 4:3 displays.
The Palette-Based Framebuffer
In this system, the framebuffer is a linear array where each pixel is represented by a single byte. This byte serves as an index into a palette of 256 RGB values. This approach forces deliberate color choices, as every asset must be quantized to this limited set of colors to maintain visual consistency.
Raycasting and the Colormap Lighting System
The engine is a traditional raycaster using the DDA (Digital Differential Analyzer) algorithm. To solve the problem of lighting without shaders, the developer implemented a colormap—a 2D matrix of palette indices.
- The Problem: Finding a darker shade of a color in a 256-color palette at runtime is too computationally expensive to do per-pixel.
- The Solution: A precomputed colormap where each of the 256 colors has 31 pre-calculated darker variants (32 shade levels total).
- Implementation: The developer used the Oklab color space and a perceptual distance formula to find the closest palette match for each shade, applying a "hue shift" toward warmer tones for darker colors to avoid a cold, lifeless look.
- Optimization: The colormap row index is calculated only once per screen column for walls, once per screen row for floors, and once per sprite, resulting in O(1) lookup time per pixel.
Asset Creation Pipelines
To maintain a high level of polish while working as a solo developer, the project employs three distinct asset pipelines: pre-rendered 3D models, hand-drawn pixel art, and procedural generation.
Pre-rendered Sprites
Complex animations are created as 3D models in Blender, rigged and animated, then rendered to textures via Python scripts. To avoid the "blurry" look typical of downscaling, the developer uses Blender's compositing nodes to increase contrast and clarity before passing the image through a palette quantization script that maps pixels to the closest Oklab color in the game's palette.
Hand-drawn Assets and Pixel Scale
Critical elements, such as the HUD and the status bar face, are hand-drawn in Aseprite and Affinity Photo. A key technical requirement is the maintenance of a consistent pixel scale. In Catlantean 3D, one world unit equals 64 pixels. All sprites are designed relative to this scale to prevent the visual jarring that occurs when assets of different resolutions are mixed.
Procedural Texture and Animation Generation
For repetitive textures and complex animations, Python scripts are used to automate the process:
- Procedural Textures: Scripts combine heightmaps, noise maps, and grime maps to generate variations of base materials, which are then palette-quantized.
- Gibbing System: Enemy death animations are generated via Voronoi decomposition. A sprite is partitioned into chunks based on random seed pixels; these chunks are then assigned physics (velocity, spin, gravity) and "wound" bleeding effects based on a BFS (Breadth-First Search) that blends boundary pixels toward a blood-colored palette ramp.
- Pre-baked Particles: Effects like teleport flashes are synthesized by computing radial energy fields (core, rays, rings, and noise) and baking them into frames, avoiding the need for a runtime particle system.
Tooling and Map Editing
Because general-purpose tools like Tiled lacked native support for the game's specific needs—such as light level painting per cell and specific cell flags—the developer built a custom map editor using wxPython.
This editor integrates with the engine via a library called pybast, which provides Python bindings for C++ engine internals (via pybind). This allows the editor to share the same serialization and fixed-point coordinate logic as the game engine, ensuring a tight ecosystem between the development tools and the final product.
Community Insights and Technical Context
Discussion among developers on Hacker News highlighted the historical context of these techniques. Users noted that the 320x200 VGA mode was popular because the 64,000-byte buffer fit perfectly into a 16-bit segment, simplifying memory addressing on early CPUs.
Other contributors compared the engine's architecture to early shooters like Wolfenstein 3D, noting that the use of perpendicular walls and constant floor/ceiling heights is characteristic of early raycasters, whereas later titles like Doom moved toward BSP (Binary Space Partitioning) engines for greater geometric flexibility.