Twenty Dungeons from One Generator: Sequence Graphs, ASCII Rooms and Solvers That Say No
A dungeon is written as a graph of rooms and locks, laid out on a grid, stitched from ASCII templates, and checked by a solver before it's allowed to exist. The mechanics are data blocks the game runs.
The question #
On day five, with one hand-built dungeon (the Crypt) and a second one in progress (the Sun Tomb), the human asked: "Should we build a procedural dungeon generator? [...] I guess it's ok to build such a tool once and not at runtime." They linked two open-source generators as inspiration and then, a few minutes later: "I would not use the tools I linked, we should build our own. Come up with 20 dungeons [...] Happy to have a [popular Korean MMO] inspiration there including some puzzles. I would also like to have some form of labyrinth as a puzzle for some dungeons."
Two decisions in that request shaped everything. Offline, not at runtime: the generator produces a map file that a human reviews and the game loads like any other map, so a bad seed never reaches a player. And puzzles: a generator that only places rooms and corridors would be easy; one that places a lever behind a door that the lever opens would be a bug factory. So the generator had to prove every dungeon could be finished.
The pipeline #
tools/dungeongen/ is plain Python (3.13 and Pillow, about 3,600 lines). A spec per dungeon becomes
the map JSON the game reads, plus a top-down overview PNG to review:
python tools/dungeongen/dungeongen.py crypt_of_whispers # spec seed -> map + overview
python tools/dungeongen/dungeongen.py crypt_of_whispers --search 12 # overviews of 12 seeds, writes no map
A seed is a layout: the same spec and seed always give the same dungeon. The workflow is to generate a sheet of seeds, pick one by eye, and write it into the spec.
1. The spec is a sequence graph #
A dungeon starts as rooms with roles (entrance, fight, key, puzzle, miniboss, treasure, maze, boss) and edges between them, optionally with a lock. Here are the Crypt of Whispers' edges:
{"from": "entrance", "to": "ossuary", "flow": "E"},
{"from": "ossuary", "to": "charnel", "gap": [4, 7]},
{"from": "ossuary", "to": "whispers", "flow": "E", "lock": {"lever": "winch"}, "gate_id": "portcullis",
"label": "Chained Portcullis", "hint": "Chained shut. A winch somewhere must hold the chain."},
{"from": "whispers", "to": "warden", "lock": {"puzzle": "whisper"}, "gate_id": "crypt_door", "look": "door",
"label": "Sealed Crypt Door", "hint": "The door will not move while their shadows are bound. Listen to the whispers."},
{"from": "whispers", "to": "reliquary", "lock": {"any": [{"flag": "whisper.flawless"}, {"boss": true}]}, ...},
{"from": "warden", "to": "lords", "lock": {"cleared": "warden"}, "gate_id": "bone_gate", "label": "Bone Gate", ...}
Locks are a small condition language: a lever, a solved puzzle, a cleared room, a key, a flag, the boss
dead, and any / all / not to combine them. The reliquary above opens if you lit the candles without
a mistake or if you killed the boss: a reward for doing the puzzle well, without locking out a party
that didn't. The game evaluates the same language at runtime, so what the generator proved is what the
game checks.
2. Rooms: templates or shapes #
Set-piece rooms are drawn as ASCII templates, one character per 2 m cell, so a room can be sketched in a text editor. This is the Hall of Whispers, the Crypt's puzzle room:
.......DD.......
.O............O.
................
....C......C....
................
................
D......W.......D
D..............D
................
....C......C....
................
.O..b......b..O.
................
D marks door sockets, C the candles of the sequence puzzle (numbered in reading order), W the
whispering ghost who gives the hint, O pillars, b bone piles. The legend has 30-odd marks by now:
levers, chests, pressure plates, light-beam emitters, fox statues, valves, spike traps, dart slits, war
gongs, a cauldron and its ingredient rack, watcher monks, anvils, sluices. Ordinary rooms are generated
from a shape instead (rectangle, L, round, octagon, pillared hall, maze) with decor, monsters and devices
placed by rules: "a lever, far from the doors".
3. Layout and corridors #
Rooms go on a grid of 2 m cells, grown from the entrance in tree order: each child on one side of its
parent with a corridor gap and a sideways jog. If a child fits nowhere, the search backs up and moves
earlier rooms. Corridors are routed with A* over 2x2-cell windows, so they're exactly 4 m wide, and turns
cost extra. One rule matters more than it looks: a corridor never touches another room or corridor
except at its two doorways, so a locked door can't be walked around. Extra loop edges are routed
afterwards for shortcuts.
4. Validation, or: the solver says no #
This is the step that makes puzzles safe. With every gate shut, the floor must split into exactly the groups of rooms that the open edges join (no leaks through a wall). Every room is one piece, every spawn stands on floor, there's a player spawn and a boss. Then the lock solver runs: a breadth-first search over states of (room, flags). Events inside rooms set flags: clearing a room's monsters, pulling a lever, solving a puzzle, standing on a pressure plate (which come back up, so those flags clear again), killing the boss. Gates latch open once their condition holds.
The solver must reach the boss, visit every room, and find no soft-lock state: no reachable state from which the boss can no longer be reached. With plates, valves and toggles that's a real risk, not a formality. If any check fails, the generator prints the problems, draws them on the overview, and refuses to write the map. The biggest search so far is the Hall of Mirrors: about 47,000 states, no soft lock.
Six kinds of labyrinth #
The human asked for labyrinths. A plain maze is a chore, so each labyrinth dungeon got a rule that changes how you read it, and each rule got its own solver:
| Dungeon | Kind | What the generator proves |
|---|---|---|
| Foxfire Maze | guide: a white fox runs the true way; false foxes lead into dead ends | the fox's path is a real walk through the braided maze, and every false fox starts on a fork of the true path and ends in a dead end |
| Red Hand Mines | dark: you see a few metres; kindled totems clear the dark; minecart shortcuts | the solver walks rail lines both ways while their condition holds |
| Ossuary of Bells | shifting: a bell tolls 1 to 3 times, and every bone wall marked with that many skulls turns | from every reachable (cell, wall state), the exit can still be reached whatever the bell tolls; every passage opens at some point; the way through needs 2 to 5 knells |
| Mirage Palace | illusion: fake walls and fake openings; a carried Sunstone lantern shows the truth | trusting your eyes alone never gets you through, and the false maze shows a way that isn't there |
| Glacier Halls | ice slide: on Mirror Ice you slide until something stops you; boulders can be pushed | every door reaches every other, the main crossing takes a set range of slides, and no state is stuck |
| Hall of Mirrors | split: twin mazes; a lever in one opens bars in the other; mirrors between them | the 47,000-state search above, with a party or solo |
The shifting maze is a good example of how much the solver carries. Walls sit on pivots; each blocks one of two passages next to a post. A knell of k tolls turns every wall of section k. The planner places the walls on a stamped maze where both passages are floor, then a 0-1 breadth-first search over cell x section bits checks the whole space. The game then plays the same rules the solver checked; for the ice, the commit message calls them "shared rules with the generator's solver".
Mechanics as data blocks #
The game side follows the same rule as skills: a new mechanic is a new building block plus a test,
never a script for one dungeon. The generator writes a mechanics block into the map (devices,
puzzles, rules, objectives), and a server-side DungeonScript runs it. By the 20th dungeon there were 31
device kinds: gates, levers, plates, light beams, valves and water, spike traps, gongs, totems, mine carts,
a cauldron and ingredients, shifting walls on a bell, illusion walls and a lantern, ice and boulders,
watcher monks with sight cones, sluices and anvils for lava, rising lava, warps between floors, key
carriers, seals, waves to survive, chests. Rules are "when this condition holds, do these actions", and
actions are things like spawn, say, open, set a flag, toll the bell or curse everyone nearby.
The blocks compose. The Bog Witch's Hollow asks you to brew her potion: three mini-bosses each drop an
ingredient, her recipe book reads out an order rolled per instance, and a wrong ingredient boils over
into a curse and three hexed toads. That's an ingredient device, a cauldron device, a brew puzzle and
a curse action, none of which knows about witches.
A few blocks that came out of specific requests:
- Walls 6 m high. The first remake had low walls, and the human pointed out the obvious: "The walls of the dungeons need to be higher. Otherwise people will just look over." With tall walls, the camera side is cut away near your character so you can see in.
- Weekly chests. Each Demon Tower floor has a coffer that opens once per character per week.1 Monday 00:00 UTC on the server's clock, saved with the world. An echo of that MMO.
- A boss copied from you. At the end of the Hall of Mirrors, "Your Shadow" steps out of the great mirror with the look, school, skill ranks and stats of the strongest player in the room, darkened.
The twenty #
| # | Dungeon | Levels | Map | Idea |
|---|---|---|---|---|
| 1 | Crypt of Whispers | 8-12 | Sanctuary Vale | light the candles in the order the ghosts whisper |
| 2 | Foxfire Maze | 12-16 | Sanctuary Vale | follow the fox with a shadow |
| 3 | Drowned Temple | 16-20 | Shadow Marsh | three valves raise and lower the water |
| 4 | Bog Witch's Hollow | 20-24 | Shadow Marsh | brew the witch's potion in the right order |
| 5 | Goblin Warren | 22-28 | Shadow Marsh | spike traps, alarm gongs, a king warded by shamans |
| 6 | Red Hand Mines | 28-32 | Orc Valley | dark maze, light totems, mine carts |
| 7 | Orc Stronghold | 32-38 | Orc Valley | break the gate under waves; generals left alive buff the warlord |
| 8 | The Black Abbey | 38-44 | Duskmoor | unseen haunts until a candle finds them; break the ritual's anchors |
| 9 | Ossuary of Bells | 40-44 | Duskmoor | count the tolls; the walls turn |
| 10 | Sun Tomb | 46-52 | Sunscar Desert | light beams and pressure plates |
| 11 | Spider Hollows | 44-50 | Sunscar Desert | webs, egg sacs, a boss that drops from the ceiling |
| 12 | Mirage Palace | 46-50 | Sunscar Desert | carry the lantern; trust nothing else |
| 13 | Glacier Halls | 52-56 | Frostpeak | slide on the ice, push the boulders |
| 14 | Frost Monastery | 54-60 | Frostpeak | stay out of the monks' sight; braziers against the cold |
| 15 | Old King's Forge | 58-62 | Ember Wastes | route the lava to heat the anvils without cutting off your path |
| 16 | Caldera Depths | 62-65 | Ember Wastes | climb before the lava does |
| 17 | Demon Tower | 66-76 | Hellmouth Rift | nine floors, a task each, weekly coffers |
| 18 | Chamber of Seals | 70-75 | Hellmouth Rift | break seven seals in the order the litany gives |
| 19 | Hall of Mirrors | 80-85 | Shadowlands | twin mazes; your own shadow at the end |
| 20 | Shadow Citadel | 86-90 | Shadowlands | the final raid |
Nineteen come from specs. The Sun Tomb was hand-built before the generator existed; its walls were later regenerated from the same kit, and its plates, beams and doors were merged into the same mechanic blocks so there's one system, not two.
How they were built #
After the generator core and the Crypt remake as a pilot, the dungeons were built in batches of three or four per agent, at most about four agents at once. Each dungeon came with a spec and a seed picked from a search sheet, its own kit theme (20 themes, 458 pieces from a parameterised Blender script, merged per chunk at load), monsters reusing existing bodies recoloured with an accessory, a boss with its own silhouette, its own music track, a portal by a landmark of its map, tests, and a folder of screenshots: the gate, every room, each mechanic in action, the boss and the overview.
What we don't know yet #
The generator proves a dungeon can be finished. It doesn't prove it's fun, and nobody has played most of them yet. The combat simulation from the testing post can fight every boss and every room, but its bots walk in straight lines, so anything behind a maze, a rail line or deep water ends "stuck", and dungeon run times need a human with a stopwatch. Every batch report ends the same way: timings, damage and puzzle difficulty are "a first guess, not playtested". That's honest, and it's the next step.
Takeaways #
- Generate offline, review by eye, load like any map. A seed you picked is a layout you saw.
- Write dungeons as graphs with locks, and use the same condition language in the generator and the game.
- Make the generator refuse. A solver that rejects soft-locks is cheaper than a bug report from a party stuck behind a door.
- Give every labyrinth a rule, and every rule a solver. Six kinds of maze, six proofs.
- Mechanics are blocks. A witch's brew is four generic pieces with a story on top.
What's next #
Twenty dungeons need a reason to go into them. Next: the lore, and a story that unfolds as you level.