,

I Found My 1991 QBasic Dungeon Crawler on a Backup CD and Mass Produced the Sequel with AI

Let me tell you a story about a CD-R, a dead language, and the 35 years it took me to build a sequel. I was digging through an old archive, one of those backup discs you burned in the early 2000s because hard drives were expensive and you had this irrational conviction that your accounting…

Let me tell you a story about a CD-R, a dead language, and the 35 years it took me to build a sequel.

I was digging through an old archive, one of those backup discs you burned in the early 2000s because hard drives were expensive and you had this irrational conviction that your accounting files and half-finished code deserved immortality. Somewhere between tax spreadsheets and a folder optimistically named PROJECTS, I found it. Four QBasic source files. DUNGEON.TXTDUNGEON1.TXTDUNGMAKE.TXTEQUIP.TXT. And in the comment header of the first one, four words I hadn’t thought about in decades:

' * The Dungeons of Dreagoth *
  1. I was writing dungeon crawlers in QBasic on a 286. No internet. No Stack Overflow. Just the QBasic help system, a spiral notebook full of hex codes, and the absolute certainty that if I could just get the corridor carving right, I’d have built something worth playing.

I did not get the corridor carving right. The original only connected the up and down stairways with a DFS path, which meant most rooms were orphans, generated, rendered on screen, and completely unreachable. I didn’t know that at the time. I thought I was a genius. The dungeon looked great on the 80×24 screen. You just couldn’t walk to half of it.

I sat with that for a minute. Thirty-five years of not knowing that the game I was proud of was mostly decorative. There’s something almost poetic about that, all that effort carving rooms that no one, including me, ever visited.

Reading Dead Code Like Archaeology

Opening those files in 2026 was like reading a letter from a younger, more optimistic version of myself who hadn’t yet learned what “technical debt” meant because he was too busy creating it.

The tile system was the first thing that caught my eye. Every dungeon cell was a hex byte, 0xFF for walls, 0x14 for rooms, 0x07 for stairs going up, 0x01 for a door with north/south traffic. There was even a bit-flag system for locked doors (0x80) and magically locked doors (0x40). In 1991. On a 286. I was OR’ing flags onto door bytes before I knew what a bitmask was formally called.

' : 1111|1111 - &hff - 10'x10' Section of wall
' : 0000|0001 - &h01 - Door, Traffic North/South
' : 1000|0000 - FLAG - Locked Door
' : 0100|0000 - FLAG - Magically Locked Door

The equipment file was 134 items across seven categories, swords, armor, provisions, the works, stored in a format that can only be described as “tab-delimited if the person pressing tab had a nervous condition.” Underscores for spaces. Inconsistent column widths. But functional. Each item had a name, a category, and a price. No damage dice, no AC bonuses. It was a price list, not a game system. The actual combat, if it ever existed, lived in source files I apparently didn’t bother backing up. Or never wrote.

The dungeon generator itself was surprisingly competent for a teenager’s QBasic. It initialized the grid with walls, punched rooms into it with collision detection, then carved corridors. The DUNGMAKE.TXT file contained a first-person corridor renderer using QBasic DRAW commands, the kind of wireframe view you’d see in Wizardry or Eye of the Beholder. I had ambitions. I just didn’t have the follow-through to connect them all into a finished game.

Thirty-five years later, I had Claude.

The Pitch

The project concept was straightforward in the way that leads to six uninterrupted hours of coding: take the 1991 dungeon generator, port it to Python, fix the unreachable rooms, build a proper game around it, and let an AI Dungeon Master handle the atmospheric narration. A TUI dungeon crawler with D&D mechanics, fog of war, and Claude whispering dark fantasy prose every time you walk into a room.

I called it Dungeons of Dreagoth II, because sequels released 35 years late deserve the dignity of a Roman numeral.

Three constraints shaped everything. First: the hex codes are sacred. The 1991 tile encoding survives into the Python IntEnum unchanged. 0xFF is a wall in 1991 and 0xFF is a wall in 2026. Non-negotiable. Second: AI handles narration, not mechanics. Claude describes rooms and narrates combat, but it never touches the dice. The d20 decides whether your sword hits. Not the model. Third, and this one I only understood fully while wiring it up, every AI call needs a fallback. The game has to be 100% playable without an API key. If Claude is down, if the key is expired, if the network is gone, you still play. I didn’t want to build a game that required a subscription to function. That felt wrong in a way I couldn’t fully articulate until I was staring at the dependency and realized I was about to make a dungeon crawler held together by someone else’s uptime SLA.

Building the Thing

The grid came first. The original was DIM SHARED DungeonData%(1 TO 80, 1 TO 24). The sequel is an 80×40 numpy uint8 array, same concept, wider canvas. I expanded the vertical space because 24 rows felt claustrophobic once you had 25 rooms to place.

The room placement algorithm survived nearly intact: pick a random position and size, check for collisions with existing rooms with a 1-tile buffer, retry up to 500 times if it overlaps. Brute force. But it works perfectly for dungeon generation, which is exactly the kind of problem where brute force is the correct answer and anyone who tells you otherwise is optimizing prematurely.

The corridors I actually fixed. The original connected only the up and down stairs via DFS, which is why most rooms were unreachable, they weren’t on the path, so they didn’t get connected. I replaced it with Prim’s Minimum Spanning Tree on room centers using Manhattan distance. Every room connects to at least one neighbor. No orphans. The dungeon I wanted to build in 1991 but didn’t have the vocabulary for.

Then fog of war. Recursive shadowcasting, the 8-octant algorithm from RogueBasin that makes every roguelike developer feel like they understand computational geometry until they have to debug it. My first implementation returned exactly one visible tile: the player’s position. I stared at it for an embarrassingly long time before I found it: the slope calculation was using positive j where the algorithm requires dy = -j. One sign. One character. An hour of my life. That’s the kind of bug that makes you question your entire career until you fix it and feel briefly invincible.

With the dungeon walkable I needed things that wanted to kill you. Four classes, four races, six ability scores rolled 4d6-drop-lowest. Standard D&D. Combat is turn-based: roll d20, add your attack bonus, compare to monster AC. Natural 20 doubles damage. Natural 1 is a fumble. Monsters fight back by the same rules, with special abilities layered on, Giant Spiders poison, Ghouls paralyze, Wights drain levels, Trolls regenerate. Fourteen monster types scaling from Giant Rats on level 1 to Minotaurs on level 10. The equipment database I rebuilt from scratch rather than parsing EQUIP.TXT, the original’s tab-delimited chaos wasn’t worth fighting, and I needed damage dice and AC bonuses that 1991 Ron never got around to specifying.

Wiring in the AI, What Actually Happened

This is where I expected the Claude Code Chronicles story to write itself. And honestly? It was messier than the retrospective makes it sound.

The DungeonMaster class architecture was clear from the start: check SQLite cache first, try Claude Sonnet second, fall back to templates third. The cache key is a SHA-256 hash of content type plus context. Same room, same level, same description every time. You never pay for the same narration twice. That part went cleanly.

What didn’t go cleanly: the prompt engineering. I had a precise picture of what I wanted, dark fantasy, second person, one to three sentences, no emojis, atmospheric color, never duplicating what the combat engine already said. Getting Claude to consistently stay in that lane without either being too terse or drifting into purple prose took more iteration than I expected. I’d get something like “The goblin’s eyes widen as your blade finds the gap in its rusted armor” and think yes, that’s it. Then the next call would return two sentences of lore dump about goblin society. The system prompt went through probably eight versions before it stabilized.

What Claude was genuinely good at: NPC dialogue. I gave it eight NPC templates, merchants, quest givers, a sage, wanderers, and it gave each one personality without me asking. The sage sounds different from the merchant. The wanderer sounds like someone who’s been down here too long. That kind of texture would have taken me days to write by hand. Instead I got it in the time it took to write the prompt.

The fallback templates are JSON files with pools of generic descriptions per category. “A damp chamber echoes with distant dripping.” Not Shakespeare. But the game plays identically with or without the API key, which was always the point. AI is seasoning. The dungeon is the food.

Death, Polish, and the Bonfire Mechanic

Save/load went in as responsible engineering: JSON serialization across five manual slots plus autosave. Items stored by ID rather than by value so balance changes in equipment.json automatically propagate to existing saves. Version field in the schema because I’ve been doing this long enough to know you’ll always need it.

The first-person ASCII view was pure indulgence. DUNGMAKE.TXT from 1991 rendered wireframe corridors with QBasic DRAW commands. The sequel does it with ASCII art in a Textual panel, Tab toggles between top-down map and first-person. I didn’t need it. I wanted it. Sometimes that’s enough.

Resurrection works like this: when you die, if you have gold, the dungeon takes its cut. Your equipment drops at the death site as a treasure pile. You respawn at the up stairs with half HP. Zero gold means permanent death. The weight of that is in walking back through floors you already cleared, under-equipped, to reach your corpse. Stairs heal HP and restore spell slots, the rest-at-the-bonfire mechanic, the rhythm that makes dungeon crawling feel like pressure and release rather than just attrition.

What the Hex Codes Mean

Here’s the detail I keep coming back to: 0xFF meant “wall” when I was a teenager on a 286, and it means “wall” in the Python IntEnum running on hardware that would have seemed extraterrestrial in 1991. The bit flags I invented for locked doors before I knew what bitmasks were formally called, they’re still there, still working, still OR’d onto the base tile value.

Thirty-five years of software engineering taught me better patterns, better languages, better tools. But the fundamental data model? A grid of hex bytes where each value means something specific? That teenager got it right without knowing why it was right. He just needed a compact representation that fit in a 286’s memory and could be rendered in QBasic. The constraints forced clarity, and clarity turns out to be durable.

There’s something uncomfortable in that realization if you sit with it long enough. All the sophistication I’ve accumulated, the abstractions, the frameworks, the career’s worth of lessons about what scales and what doesn’t, and the encoding I reached for as a kid with no formal training is the same encoding I’d reach for today. The orphaned rooms were a bug. The tile system was correct. He couldn’t tell the difference between them.

I wonder how much of my current work looks the same way from thirty-five years out. Which parts are the orphaned rooms, and which parts are the hex codes?

Claude wrote the prose. Claude helped debug the shadowcasting and generate NPC dialogue and narrate the quest completions. But Claude didn’t design the game. The game was designed in 1991, sketched in a spiral notebook, and encoded in values that survived three decades on a CD-R wedged between tax returns.

The sequel took a single day. The original took a semester and was never finished.

I’m not sure which one I’m prouder of.

The full project is on GitHub:

Leave a comment