The author frames the Catlantean project as a deliberate exercise in stripping away every modern abstraction — no OpenGL, no Vulkan, no FPU — to force a confrontation with the underlying math. By rebuilding fixed-point arithmetic, affine texture mapping, and per-vertex baked lighting from scratch, he argues you internalize the constraints that shaped an entire era of game aesthetics.
Argues that a generation of developers who learned 3D by binding vertex buffers and trusting drivers hits a wall when frame times spike or shadow maps shimmer. Software rasterization is positioned as the graphics-programming equivalent of assembly — painful but the only path to genuine understanding of the abstraction stack.
Staniks methodically shows that swimming textures came from affine mapping (perspective-correct division was too expensive), vertex snapping came from fixed-point math (no FPU on the PS1), and color banding came from 5-bit channels with ordered dithering. Every signature visual tic of the era was a direct concession to a transistor budget rather than a deliberate stylistic decision.
Commenters in the 94-comment thread surfaced the Crash Bandicoot postmortem, Michael Abrash's Graphics Programming Black Book, and Chris Hecker's 1995 Game Developer Magazine articles on perspective-correct texture mapping. The consensus framing was that this kind of from-scratch reconstruction is valuable specifically because it forces engagement with the same problems those canonical texts grappled with.
A blog post titled *Making Graphics Like it's 1993* — the first entry in a series chronicling a project called Catlantean — climbed to 572 points on Hacker News this week. The author, Staniks, is reconstructing the rendering pipeline of an early-90s console (think PlayStation, Sega Saturn, N64-adjacent) entirely in software: no OpenGL, no Vulkan, no Metal. Just a framebuffer, integer arithmetic, and a refusal to use anything the era didn't have.
The post walks through the foundational decisions: fixed-point math instead of floating-point (because the original PS1 had no FPU), affine texture mapping instead of perspective-correct (because per-pixel division was prohibitively expensive), 5-bit-per-channel color with ordered dithering, and per-vertex lighting baked at load time. Every aesthetic tic that screams "1996" — the swimming textures, the vertex snapping, the chunky color banding — was a direct concession to a transistor budget, not a stylistic choice.
The HN thread quickly turned into a graphics-programming master class. Commenters surfaced the *Crash Bandicoot* postmortem, Michael Abrash's *Graphics Programming Black Book*, and the canonical Chris Hecker articles on perspective-correct texture mapping from *Game Developer Magazine* circa 1995. The consensus: this is the kind of project you do not because it's useful, but because it forces you to internalize what the GPU is actually doing on your behalf.
There's a generation of developers — and at this point it's most of them — who learned 3D graphics by binding a vertex buffer, writing a shader, and trusting the driver to do the rest. That works until it doesn't. When your frame time spikes, when a mesh z-fights itself into oblivion, when a shadow map shimmers no matter how many filters you stack on it, the abstraction stops being helpful and starts being a wall.
Software rasterization is the closest thing graphics programming has to assembly: slow, painful, and the only way to actually see what a pixel costs. When you're the one writing the inner loop that interpolates U and V across a scanline, you stop wondering why texture filtering matters. When you implement your own depth buffer and watch precision collapse at the far plane, you stop reaching for a deeper Z-buffer as a first instinct and start thinking about reversed-Z, logarithmic depth, or just moving the near plane.
The author's choice of 1993 specifically is interesting. That's the inflection year — before the Voodoo 1, before hardware T&L, before the very concept of a "shader." It's also the year *Doom* shipped, which means the engineering tricks of the period are still legible to anyone who's read the *Doom* source: BSP trees, span buffers, fixed-point everything. The PS1's affine texture mapping wasn't a bug; it was the rational choice when a perspective divide cost more cycles than the rest of the triangle setup combined.
The HN discussion also flagged a more recent point: the indie aesthetic that's now called "PS1-core" or "low-poly horror" — games like *Signalis*, *Crow Country*, *Mouthwashing* — is built on tooling (Unity, Godot, custom Three.js shaders) that fakes these artifacts on top of modern pipelines. Most of those fakes are wrong. The vertex jitter is usually applied in clip space when it should be applied in screen space at integer precision. The texture warping is usually a UV distortion when it should be a missing perspective divide. The dithering is usually a post-process when it should be in the color quantization itself. A real software pipeline gets all of these right by default, because they're not effects — they're consequences.
Most readers will never write a software rasterizer in production, and they shouldn't. But there's a transferable skill here that's becoming rarer: the ability to reason about a pipeline as a sequence of arithmetic operations on memory, rather than as a black box you configure.
If you're shipping anything performance-sensitive on the GPU — a custom render pass in Bevy, a compute shader in WebGPU, a Metal pipeline for an iPad app — the bottleneck almost never lives where your profiler first points. It lives in bandwidth, in cache misses on the vertex stream, in the cost of a branch inside a fragment shader that you assumed was free. The exercise of writing a rasterizer by hand is the cheapest way to build the mental model that makes those numbers stop being mysterious. Treat it as the graphics-programming equivalent of writing your own allocator, or building a toy database: you won't ship it, but you'll never read a flamegraph the same way again.
For frontend devs specifically: WebGPU shipped in stable Chrome two years ago and is finally in Safari. The same constraints the PS1 had — limited memory bandwidth, expensive per-pixel work, fixed-function blending — map almost directly to mobile GPU tile-based rendering. The lessons port. If you've ever wondered why your three.js scene tanks on iPhone but flies on a M-series Mac, the answer is in the same family of trade-offs Catlantean is rebuilding by hand.
The blog promises a multi-part series: future posts will cover the actual triangle setup, the clipping pipeline, lighting, and eventually a full asset toolchain. If it lands the way the first post did, expect it to become the modern equivalent of Hecker's texture mapping series — the canonical reference the next time a junior asks why their depth buffer is doing something cursed. Bookmark it now; you'll cite it later.
This is taking a lot of inspiration from Doom, but the actual raycasting engine is more like Doom's predecessors, the most well-known of which is probably Wolfenstein 3D: perpendicular walls, constant floor and ceiling height. Wolf3D didn't have textured floors and ceilings because of perf
Graphics programming in the early to mid 1990s was pretty fun: write pixel data into the memory-mapped video RAM and it appears on the screen! A pointer to 0xA0000 was all you needed - no API or anything. The reason for the non-square-pixel 320×200 VGA mode they mention was that the video buffer too
If you want to play with software rendering, here's probably the shortest code that will get an ARGB8888 2D array from main memory to the screen efficiently for all platforms using SDL2 in C https://gist.github.com/CoryBloyd/6725bb78323bb1157ff8d4175d... you'll need to
I just noticed that this might be one of the rare shooters with a female protagonist: the cat has a calico pattern, and those are almost always female (https://en.wikipedia.org/wiki/Calico_cat).
Top 10 dev stories every morning at 8am UTC. AI-curated. Retro terminal HTML email.
Great article. I particularly enjoyed the approach to creating gibs. Although it was a tech demo, I created something like this around the mid 90s. One thing I did that I don't see mentioned in this article was I used 8x8 (or 16x16) light maps on the textures, which allowed me to easily have th