Generating machines in dungeons by room kind and theme
Generated cyberpunk, steampunk and eldritch floors now stand juice vats, boilers, pumps and shrines, and the five tour dungeons build from their seeds again.
A generated dungeon that is given a theme now stands machines in its rooms. Cyberpunk floors get a juice vat you can walk up to, small vats, reservoirs and pumps. Steampunk floors get boilers and pumps, and eldritch floors get shrines. House and fantasy floors get none, because no machine model is installed for them. I also got the five theme tour dungeons building from their seeds again.
How does a room get its machine?
Until now a machine only stood where a hand-written row in content/machines.json named a floor and a square. A vat hall the generator made had props and nothing else. I decided the generator should pick machines by the room’s kind and the dungeon’s theme, and that the party should be able to walk up to a generated juice vat. Training at the vat comes later.
The generator has a table of which pieces a room kind may hold, per theme, in the order they are tried:
PLACE = {
"cyberpunk": {"vat": ["juicevat", "juicevatsmall", "reservoir"],
"machine": ["reservoir", "juicevatsmall", "pump"],
"sump": ["reservoir"], "pipe_gallery": ["pump"]},
"steampunk": {"vat": ["boiler", "pump"], "machine": ["boiler", "pump"],
"sump": ["pump"], "pipe_gallery": ["pump"]},
"eldritch": {"vat": ["shrine"], "machine": ["shrine"], "shrine": ["shrine", "shrine"]},
}
Most of those room kinds came in with the eight new ones the same day. A theme that isn’t in the table writes nothing.
The rules, in the order they run:
- A cyberpunk floor with a vat hall gets exactly one juice vat, in the first vat room (in the order of the rooms’ corners) that has space for it. Its row says the body is the opening’s drum and that the party spends juice at it, the same two fields the hand-made Breakout Site’s vat has.
- Every room stands one piece, two if it has at least 30 squares and three if it has at least 48. Where the table lists the juice vat and one already stands on the floor, a small vat goes there instead.
- Each piece faces the middle of its room. A two-square piece faces only north or south, because turning it east or west would turn its block.
Those counts, and which piece goes in which kind, are guesses. They are on the work list for me to settle.
When do the machines get laid?
After the stocking. If they went first, the stocking would have to know about them, and its choice of which try of a floor to keep could change. Laying them last means nothing already stocked moves.
The squares come from tour_build.machine_rows(), the function the tour already used, so I didn’t copy it. A piece and a ring of one square round it must all be inside one room, on one height, off any curve, and clear of everything the sheet holds: packs, chests, keys, doors, items and stairs. They also have to be clear of the ways between heights. The ring is the margin the game’s dresser and a pack’s members already keep away from a machine. After a juice vat is placed, the nine squares round it are marked used, so the next piece in that room stays clear of its ring.
Nothing is written into the sheet. The same seed lays the same rows every time, which the test checks.
Where do the rows live?
The other option was for the generator to write into content/machines.json. I went with putting the rows in the generated file itself, a machines list on the file for floor 1 and on each entry of floors for the others. A generated dungeon then carries everything it needs, and nothing in the game’s table is rewritten per dungeon.
On the game side the change is one function, DungeonMachines.rows_of(). It returns the table’s rows for the plan id, then the plan’s machines. placed() goes through it. The schema has a new dungeon_machine shape with piece, theme, state, x, y, facing, on, name, solid, use and why. The game still drops a row whose model isn’t installed, which is why fantasy and house stand none. The tour dungeons keep their rows in the table until I rebuild them.
What did the tests check?
The generator’s test goes over every dungeon kind at seeds 1 and 2 and every theme. It checks that:
- house, fantasy and no theme write nothing, and each theme writes only its own pieces.
- each piece is in a room kind the table names for it, with its squares and ring inside the room and nothing of the sheet on them.
- no two pieces share a square and no two rings cross.
- there’s at most one juice vat per floor, and only in a vat room.
- the sheet is the same before and after.
It also counts the cyberpunk floors with a vat room that ended up with no juice vat, because a vat hall can be too full. Most must have one.
The game’s test loads eight themed floors made by dungeon_fill.py --themed and checks the same from its side, then builds each floor and checks every placed machine is a node. It also has the party walk up to a generated juice vat, so its injector is within reach where the party stops. 518 checks passed. What stands in the fixtures:
- eldritch cold store: shrines only, 6 on the one-floor dungeon.
- steampunk lab wing, three floors: 2 boilers and a pump on floor 1, nothing on floors 2 and 3.
- cyberpunk lab wing: a juice vat, 2 small vats, a pump and 2 reservoirs.
- cyberpunk plant floor: a juice vat, 3 small vats and a reservoir.
- house plant floor: none.
Why couldn’t the tour dungeons be rebuilt?

Eight frames of the film. Top: the juice vat from the front, then close, then a small vat and a reservoir. Bottom: another reservoir, a steampunk boiler and pump, and an eldritch shrine.
The five tour dungeons are one per theme. tour_build.py at its first cyberpunk seed missed one of its wants, so for a while the tour files in the game were hand-mended copies. I traced it, and there was no fault in the generator. The layouts had moved under the old seeds.
Two game changes did it. One rule says a room of kind sunk, graded or by-kind may not have a crawl as its way in. That changed which try cyberpunk seed 12 keeps for floor 2: its spiral within lost a tie on how many rising pieces each try had. It also took away eldritch seed 35’s sunk room. Then the eight new room kinds redrew every lab wing, offices, quarters and cold store layout, so fantasy seed 43 lost its ambush vent and house seed 51 its flat duct. The tour also began asking two specs for every new kind.
A floor’s kept try is chosen by its secrets first, so some rare elements can’t be asked of every spec. No cyberpunk seed of 33 laid a gallery, and no fantasy seed of 25 laid an ambush vent. I fixed it with new seeds:
- cyberpunk 12 became 32.
- eldritch 35 became 32.
- fantasy 43 became 77.
- house 51 became 54.
- steampunk stays at 31.
Every spec now sets which room kinds it wants, and the five together ask for all 27. Cyberpunk no longer asks for a ceiling vent, since fantasy lays it with its ambush. tour_build --out <dir> writes the dungeons outside the game, so I could check the result without touching the tour files. A new test makes the five at their first seed and checks every want, every element and every room kind.
Compared with the files in the game, steampunk is almost the same dungeon: floor 2 is 33 by 45 squares instead of 33 by 47. Cyberpunk gains machine, pipe gallery and sump rooms, and its floor 1 goes from 36 by 33 to 57 by 51. Eldritch gains six kinds and loses bathroom and security. Fantasy gains collapse, office and shrine and loses bathroom and storage. House gains burrow.
I haven’t written the new tour files yet. That’s the next step, and the tours will use the generated machine rows once they do.