Skip to content

Vibing Voxxon - A Zaxxon inspired isometric voxel space shooter

img.png

I grew up on 80s arcade shooters. Asteroids, Space Invaders, Galaxian, Scramble, Defender and others all consumed an inordinate number of quarters, basically whatever allowance I had. Of all of them the one that left the most lasting impression was Zaxxon. The 1982 Sega arcade cabinet — and later its ports to whatever home computer you could convince your parents to buy — was unlike anything else at the time. An isometric scrolling shooter where you had to manage altitude to fly through gaps in walls, dodge missiles, and bomb ground targets. The shadow beneath your ship was your only depth cue. It was brutal, beautiful, and unique for its era. Mind you I always sucked at it, but I could watch the attract screen all day.

So when I was looking for a bite-sized project to try Claude Code on, Zaxxon immediately came to mind. It was something I could easily do myself, which gave me confidence that I would be in a good position to judge the output that Claude produced.

The result is Voxxon — a browser-based isometric scroller in Three.js with voxel explosions, a leaderboard, a boss battle, and more features than I originally planned.

Oh boy, here comes the slop

I love writing code. I do it for work, I do it for fun. I've shipped production systems in a dozen languages over more than two decades. I'm not intimidated by a Three.js project. Recently I've started using Claude at work, but primarily for tracking down bugs and writing one-off maintenance scripts. I wanted to see how it would handle a complete project rather small tasks. Not "fix this bug" or "write a script to transform these files" — actually sit down with it and think through architecture, iterate on design decisions, and let it do significant implementation work while I directed.

The Plan-First Approach

The first thing I learned: Claude Code is dramatically better when you plan before you code. The temptation is to jump straight to "implement X" — but spending time on a structured plan first pays enormous dividends.

Before even discussing features I laid out what I was going for: I'm a programmer first and artist... well, never really. Between my lack of art skills and Zaxxon's original aesthetic, the project called for a pixel-art style. Rather than faking the isometric world, I wanted to use a real 3D engine which naturally led to voxels as the target look. My immediate ask was for recommendations on voxel engines for the browser, but Claude pointed out that I really only needed voxels for the particle explosions I wanted. Everything else was standard geometry that just happened to look like voxels. The proposed stack was Three.js as the engine, MagicaVoxel for creating the voxel assets and a bespoke pipeline that would create .glb assets and JSON meta-data files.

From there I came up with a rough outline for functional phases to proceed through. For each major phase, I'd work with Claude to produce a detailed plan document (some saved in plans/, some were only in memory).

The first plan covered just the Three.js scene and orthographic camera setup. Getting the isometric camera angle right is tricky — you need a specific combination of Y and X rotations applied in the right order, and the math for the frustum scaling has to account for the projection correctly. By writing this out as a plan first, Claude could implement the entire phase in one coherent pass rather than iterating through confusion.

The axis convention decision shaped everything that followed:

  • X = scroll direction (the world auto-advances; player drifts within the window)
  • Y = altitude
  • Z = lateral (left/right)

This is obvious in retrospect but gets subtle fast. The camera looks along the +X direction at a 45° downward angle, which means the "right" direction in camera space maps to a diagonal in world space — important once you're placing objects and thinking about collision geometry. It also gets tricky, because the natural axis conventions of three.js and MagicaVoxel did not match. I wanted to be able to author and measure assets in MagicaVoxel but present them properly in-engine, so the code had to know in what context to use which set of axis and how to convert between the two.

Building the World

The level system went through several generations. Early on, levels were defined in JavaScript files as hand-crafted arrays of objects sorted by X position. A streaming system loaded objects ahead of the player and despawned them behind, keeping the scene lean.

But as the scope grew — two ground stages, two space stages, a boss encounter — I needed something more flexible. We eventually landed on JSON level files with a structured tile-based layout system, a wall builder for the corridor geometry, and a separate stages.json that described how levels sequenced and flowed into each other continuously (no loading screens between levels).

Claude was a useful thought partner for the level data format. We went through a few schemas before landing on one that was expressive enough for all the object types but not so complex that editing it by hand was miserable. There's a plans/json-schema.md in the repo that documents the final format. I wrote an initial draft of it and then iterated on the document with Claude before turning to implementation.

Where Claude really paid dividends was creating a level editor for this format. Laying out levels in JSON got tedious quick and writing level editing tooling can consume significant development effort in normal projects. However, with the existing game context, understanding of asset and level formats, the editor was in a functional state within an iteration or two.

The Camera Problem

The orthographic camera in an isometric setup has a property that's easy to get wrong and hard to diagnose: because the projection is parallel rather than perspective, "zooming in" by moving the camera does nothing. You have to change the frustum bounds.

I wanted a fixed world-space footprint — the player should always see exactly 384 voxels along the floor centerline regardless of window size. Vertical coverage should scale with aspect ratio. Getting this right required working through the geometry carefully. The frustum half-width ends up being 192/√2 (not 192, because the floor is rotated 45° relative to the horizontal extent we're measuring). The vertical half-height is HW / aspect.

Taking care of this kind of calculation is where Claude really shines as a collaborator. It can hold the geometric reasoning in context while implementing the resize handler, and it doesn't get the off-by-sqrt-2 error that would have cost me untold minutes of debugging.

Voxel Explosions

This was the most satisfying technical piece of the project. Zaxxon's explosions are simple sprite flashes. I wanted something more kinetic — actual geometry flying apart.

The approach: pre-process 3D voxel models (.vox files from MagicaVoxel) into shell/inner layers, then at destruction time spawn dozens of individual box meshes with velocities and gravity.

Claude helped design the .vox parser and the build pipeline script that converted models to .glb (for rendering) plus a .voxels.json sidecar (for explosion data). The explosion system itself has two phases: a brief "slow phase" where the voxels hang in space with a slight outward drift (this reads as the moment of impact before everything flies apart), followed by full physics with gravity and floor bounce.

The slow phase was Claude's suggestion, actually. I described wanting the explosions to feel weighty and it proposed the two-phase timing as a way to give the player a moment to register the destruction before the debris scatters. It was right.

Features I Didn't Plan On

Some of the most interesting parts of Voxxon weren't in the original design. They emerged from playing the game and noticing what was missing:

Real Art. I started with programmer art, thinking that was enough. I definitely did not want generative art. I like AI to take the tedium out of things I know how to do, not do things I cannot do. If an artist wants to use generative as part of their process, I have absolutely no problem with that, but I don't think generative tools in the hands of a novice will produce the same quality, if for no other reason than the person judging that quality would not be qualified for the job. I found Khayyam Akhtar on fiverr and we collaborated on multiple rounds of assets for the game. Having an artist create the assets was worth the cost for the quality it brought to the project.

Sound Effects. Claude automatically created sound effects using the browser built-in audio context without me even prompting, and their bleep-bloop nature was oddly appropriate. But it just didn't have the punch the game needed. I started browsing Pixabay and collected attribution licensed ambient and effect sounds and had Claude swap them in. If this game was headed for commercial release, I would definitely go back and hire an artist to create a cohesive soundscape, but for this demo project, this addition raised the bar sufficiently.

Height bars. Playing the early builds was disorienting — without perspective foreshortening, it's genuinely hard to tell if a flying enemy is at your altitude or not. The original Zaxxon had a height meter on the left hand side of the screen, which I replicated, but it wasn't sufficient. Honestly it wasn't sufficient in the original either. Adding a thin vertical bar dropping from each airborne object to the floor improved the depth perception challenge significantly. This was a thirty-minute detour that added an essential feature.

Pitch and roll on the player ship. The ship tilts forward when you accelerate and banks when you strafe. It's purely cosmetic but makes the movement feel alive. Claude implemented this in one session once I described the desired behavior — lerped rotation applied on top of position.

Engine plumes. Particle-ish exhaust trails behind the ship and rocket engines. Another cosmetic detail that took less than an hour.

The attract screen. I wanted the game to demo itself when idle, like an arcade cabinet. It rotates through a series of screens while scrolling random levels in the background. All the text is a pixel art font I created as a bitmap, extruded in and exported from MagicaVoxel as voxel data and turned into 3D models using the same asset pipeline.

The development pattern I quickly fell into was: I'd play the game, notice something felt wrong or missing, open a Claude Code session, describe the problem, and have an implementation to test within an hour. The iteration loop was remarkably tight and relaxing. Normally I'd play all the roles in the QA, project manager, developer iteration loop, but here I could concentrate on the game experience and leave the changes to Claude.

The Leaderboard Anti-Spoofing Problem

Once I decided to create an old school arcade high score entry system, I quickly realized that just storing that in browser state would not do. But moving that leaderboard to a server, forced me to decide how seriously to take score integrity. It's a browser game — any client-side score can be trivially spoofed. But I also didn't want to build a full server-authoritative simulation.

The solution Claude and I designed: the server assigns a JWT-signed challenge token at game start. At game end, the client submits the score with the token. The server verifies the token signature (proving the session was server- initiated) and runs basic plausibility checks — score must be achievable given each level's contents and minimum time to complete. A perfect score in two seconds gets rejected. Legitimate scores go through.

It's not bulletproof, but it raises the bar well above "change a number in the console". We also version-gate the leaderboard — every meaningful content change generates a new hash, and scores from old versions don't mix with new ones. This means a patch that changes enemy health or score values starts a clean leaderboard rather than letting old exploits persist. This is thanks to the per level validation metrics being generated directly from the stages files producing the appropriate hash and metrics as part of the asset pipeline.

What Claude Code Is and Isn't

I had tried vibe coding about 9 months ago and it was miserable. So much made up code, each iteration starting with a solid wall of errors requiring many iterations before it would even run badly. But my recent work experience had shown a remarkable maturation. I just didn't know if that would hold up through an entire project.

After building Voxxon this way, I can't recall more than maybe 2 or 3 times that code generated by Claude didn't work the first time I tried to run it. And most of the time it was remarkably close to what I had envisioned with deviations being no worse than I would have seen with a human collaborator. I also have a clearer sense of where Claude Code adds the most value and where you have to stay alert.

It's excellent at:

  • Implementing well-specified plans. If you know what you want and can describe it precisely, the implementation arrives quickly and usually correctly.
  • Geometric and mathematical reasoning. Working through 3D camera math, coordinate transforms, or collision geometry — having the derivation and the code in one pass is genuinely faster.
  • Suggesting what's missing. More than once Claude pointed out a case I hadn't considered or proposed an approach I wouldn't have reached immediately.
  • Maintaining consistency across a codebase it's read. It doesn't forget that you established a particular convention fifteen files ago.

You have to stay engaged for:

  • Subjective feel. Claude can implement a camera shake or an explosion timing, but only playing the game tells you if it feels right. The creative feedback loop requires a human in the seat.
  • Scope discipline. Claude will helpfully suggest extending a feature, adding error handling, or building something more general. Sometimes this is right. Often, for a game jam-scale project, you want the simpler thing. You have to actively resist scope creep.
  • Architectural cohesiveness. As you iterate over features, Claude, just like human developers, can devolve the original architecture with little changes here and there that make perfect sense locally but degrade the overall cohesiveness. And this isn't just a reading code hygiene thing either. The more convoluted your code gets the more expensive (in time and tokens) refactoring things gets later on.
  • Architectural decisions with long tails. The level format, the staging system, the score versioning — these choices compound. Claude is a good sounding board but the judgment call is yours.

The Result

Voxxon has three level types that flow continuously into each other: bases with turrets, silos, laser barriers, and fuel cells; space stages with enemy fighter formations; and a boss encounter. It has lives, fuel management, a score system with floating 3D point indicators, engine sounds, explosion audio, an attract screen, and a persistent leaderboard. It runs in the browser with no install, at a locked 60fps on any halfway-modern laptop.

It took about six weeks of evenings and weekends to build. I lost a couple of extra weeks procrastinating on the level building and balancing before I decided that getting this out there was more important than getting it perfect. Throughout the project I offloaded the bulk of the coding to Claude and concentrated on the design. The sessions with Claude were often genuinely collaborative rather than transactional: I'd have an idea, we'd work through whether it made sense, and I'd come away with something implemented and a better understanding of how it worked.

And, just like the original, I still suck at playing it.

Go set a High Score: voxxon.claassen.net

The source is on Github. The game is MIT licensed, Art is CC licensed. Find a bug, file an issue on Github or send me a pull request. If you think you can create better levels, try out the editor. Either start from scratch or load up existing levels from public/levels on github and submit them as an issue. Instructions for the editor can be found in the README