2.5D: 2D Gameplay, 3D Rendering, 1000+ Enemies

I promised last time I’d write up the rendering trick that lets the Godot port of DET-33 push far more enemies than the JS version. Here it is, and it’s a little unusual: the game runs its logic in 2D and its rendering in 3D. Why split them A top-down bullet-hell wants to look 2D, and all the gameplay genuinely is 2D. Physics, collision, movement, combat, skills, traps, and the entire map-generation pipeline stay in 2D where they’re simple to reason about. But drawing a thousand individual 2D sprites means a thousand draw calls, and culling what’s behind a wall means doing visibility checks on the CPU. Both were exactly the costs that capped the JS build. ...

June 20, 2026 · 3 min

Building a Modern Roguelike in 2026

I have a habit of writing one of these every few years. In 2017 it was a 7DRL experiment merging a roguelike with an FPS, and in 2019 it was ascii characters embedded into a 3d map. This year’s entry is different: DET-33, the bullet-hell roguelike I built in JS and Phaser over the last few months, is feature-complete and plays great. So naturally I’m rewriting it from scratch in Godot 4 and C#. ...

April 12, 2026 · 3 min

Procedural Levels with Compass

I spent a good chunk of last year writing about compass, the map-generation library with all the fluvial generators and cellular caverns. The whole time I was building it as a standalone library, the real motive was to eventually drop it into a game. This month I finally did, and DET-33 now generates every one of its floors through compass. Pulling the library in Wiring it up was almost anticlimactic, which is the point of having spent the time on the composition layer. DET-33 pulls compass in as a git dependency and the level code just imports the scanning digger and the template data: ...

December 21, 2025 · 3 min

Stat-Agnostic Skills

After getting the bullet behaviors in DET-33 into a good place, the next system to crack was skills: the active abilities a player picks up as they level. I’d built ability systems before and always regretted the same thing, so this time I restructured them to be stat-agnostic from the start. The mistake I keep making The tempting way to define a skill is to reach into player stats. “Healing scales with your wisdom, damage scales with your level.” It feels right and it ruins you later. Every skill becomes coupled to the stat model, balancing one thing ripples into everything, and you can’t reason about a skill in isolation. Years ago I wrote about balancing game mechanics by drawing the tree of which stats feed which. The lesson I took from doing that is that the fewer edges in that tree, the better, and skills reading stats directly add a lot of edges. ...

November 16, 2025 · 2 min

Composable Bullet Behaviors

This fall I started building a new game: a bullet-hell roguelike called DET-33, built on Phaser 3 and Vue. It’s the kind of game where the whole appeal is hundreds of projectiles on screen doing wildly different things, which means the weapon code becomes the heart of the project very quickly. Mine started as one weapon.js file, and it did not stay manageable for long. The problem is the obvious one. A bullet that travels in a straight line, a bullet that spirals, a bullet that boomerangs back, a bullet that splits on impact, and a bullet that drops an acid pool are all “a bullet,” but the branching to support them in one file gets ugly fast. So I tore weapon.js apart and rebuilt the projectiles as composable behaviors using the strategy pattern. ...

October 19, 2025 · 3 min

Composition Over Configuration in Compass

The individual generators in compass (the arroyos and rivers, the caverns and meanders) are the fun part to show off, but they aren’t what makes the library usable. The part I’m actually proud of is the composition layer that sits on top, and it owes a lot to the runtime pipeline patterns I wrote about a while ago. Shapes times features Every room is described as a shape combined with a feature. There are 24 shapes: squares and circles, but also blobs, crystalline caverns, crescents, capsules, and a handful of hallway types. There are 18 features: pillars, islands, stepping stones, pools, moats, chasms, rubble, and four kinds of river. Any shape can pair with any feature, which is 432 distinct room configurations before you’ve placed a single one. A feature is just two functions: an initialize step that works out the geometry (the meandering river feature, for example, generates a sine-wave centerline and expands around it), and a content step that decides the terrain type for each tile inside the shape. Keeping those two responsibilities separate is what lets the same river feature drop into a square room or an L-shaped hallway without caring which it landed in. ...

October 13, 2024 · 3 min

Caverns, Automata and the Meander Revisited

Back in 2015 I tackled automata generated caverns for a 7DRL, and in 2018 I wrote up the meander algorithm for rivers. Both made it into compass and both got a little sharper along the way, so I wanted to revisit them with the actual implementations in hand. Caverns The cavern generator is Conway’s Game of Life pointed at a terrain problem. It’s almost embarrassing how little code it takes. Clone the map and randomly seed roughly 55% of it as floor. Run a single Game of Life pass over the Moore neighborhood (the 8 surrounding cells). An empty cell is born as floor with 5 or more floor neighbors, and an existing floor cell survives with 4 or more. clipOrphaned keeps only the largest connected blob so you don’t get a dozen disconnected pockets. Build walls around everything walkable and force the map border to wall so the cave is sealed. One pass is enough. The birth and survival thresholds are doing the smoothing that I used to iterate several times to get, and the result is the lumpy organic cave you’d expect. ...

June 9, 2024 · 2 min

Carving Terrain Like Water

A few years back I wrote a handful of posts about faking water on top-down maps: generating 2d rivers, cliffs, and an exhumed river channel. Those one-off experiments eventually grew into a proper map library I’ve been maintaining called compass. Revisiting them now is fun because real-world geology already solved the “make it look natural” problem ages ago. I just borrow the names and loosely approximate the process. Here are three of the fluvial generators and how they actually work. ...

February 18, 2024 · 3 min

Atomic Design System In Context

Brad Frost created a design system for thinking about developing for the web as a front-end specialist. Atomic Design System is essentially boiled down into 5 separate categories: Atoms (Elements) - These are the smallest functional elements of a page, such as text labels, buttons, inputs or other HTML elements that can’t further be broken down. Molecules (Components) - Simple compositions or groups of atoms that comprise a single unit. This may be an input box with a search button next to it and a label above it. Within Brad’s book he mentions these should follow the “S” in “SOLID” from Robert C. Martin’s Principles of Object Oriented Design which stands for Single Responsibility Principle. By this Brad implies that the molecule must only do one specific thing, not necessarily that it has only one reason to change. Brad refers to molecules near the end as “simple, functional, reusable components.” Organisms (Widgets) - Comprised of potentially multiple molecules or atoms and molecules in conjunction to form a cohesive purpose. Here Brad specifically refers to organisms as “components” numerous times as well. In comparison, these may be more sophisticated components compared to molecules. Templates - Templates are simply a collection of elements, components and widgets that when together build an entire page and lays it out without actual data. You can think of these as a Class in object oriented design. Pages - Pages are instanced Templates, or Templates with actual data. The actual benefit of the design system is in it’s name. Atomic Design System helps by putting an emphasis on breaking things down into their most atomic units and creating categories of complexity that are well-named to be able to refer to them. ...

August 15, 2023 · 3 min

Pihole On-top of UDM Pro

Here are some concise steps on how to step-by-step block ads on your network with pihole’s DNS-level ad blocker. Enabling SSH on UDM Pro Go to 192.168.1.1 or wherever you’re hosting your UDM Pro Within “settings” cog, click on “system” tab Click “enabled” on SSH near the bottom Leverage “change password” button if you don’t remember it Log Into SSH & Get Unifi-OS Scripts Open Terminal and ssh [email protected] -o HostKeyAlgorithms=+ssh-rsa Jump into Unifi shell with unifi-os shell Download package curl -L https://udm-boot.boostchicken.dev -o udm-boot.deb Install package dpkg -i udm-boot.deb Enable boot script with systemctl enable udm-boot Leave Unifi shell but stay within ssh with exit Create Network Within UDM Web GUI Create a pihole network with the following settings: ...

December 19, 2022 · 3 min