Dekiro Dev Blog

Part 13 of 28

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.

Four seeds of the same spec (the Red Hand Mines): same rooms, locks and maze rules, different layouts

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.

The Ossuary of Bells as the generator draws it: rooms by role, locks in red, the shifting maze in the middle

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".

The Ossuary of Bells in play: the knell has tolled, and the walls marked with that many skulls are turning
The Mirage Palace without the lantern, and in its light: false walls turn into lines of light, hidden ones show as gold glass
Glacier Halls: on Mirror Ice you slide on the grid until a wall, a rock or a boulder stops you
The Foxfire Maze: the white fox casts a shadow; the false ones don't

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.
Wall cutaway on the camera side: before and after the cleanup
Your Shadow: a boss built at runtime from the strongest player present

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.

The Hall of Mirrors' twin mazes, connected by mirrors and levers that open each other's bars

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.