We’re pivoting. The campfire prototype proved the framework works. Now we need to build the actual game: a procedurally generated dungeon crawler. That means we need a way to define 3D spaces in code.
Iteration 1: MapLayout
First attempt was a MapLayout system — define rooms as typed objects with explicit connection graphs, adjacency matrices, and spatial constraints. It had a room class, a connection class, a constraint solver, and a layout validator.
It was overengineered. We were defining rooms for a campfire-sized prototype, not planning a city. The abstraction was heavier than the thing it was abstracting.
Iteration 2: GeometrySpec
Same afternoon, we stripped it down. GeometrySpec dropped the graph theory and used plain tables — each room is a position, a size, and a list of openings. Simpler, but still had too much ceremony. The spec format had its own parser, its own validation, its own error messages. For what? Describing boxes in 3D space.
Iteration 3: Factory
Third time’s the charm. The Factory system is the simplest version that actually works. You describe geometry as an array of entries:
{
{ class = "Room", size = {40, 20, 40}, position = {0, 0, 0} },
{ class = "Room", size = {30, 20, 30}, position = {40, 0, 0} },
{ class = "Door", between = {1, 2} },
}
The Factory walks the array, resolves each class to a builder function, and calls it. A compiler pass optimizes the output — merging adjacent walls, deduplicating shared edges, pre-computing bounding boxes. The definition is dead simple; the compiler handles complexity.
What makes Factory work where the others didn’t:
- No custom spec format — It’s just Lua tables. No parser to maintain.
- Class resolution is pluggable — The Factory doesn’t know what a “Room” is. It asks a Resolver (coming on Day 17). New geometry types are registered, not hardcoded.
- Compiler is optional — You can skip optimization for debugging and get unoptimized but correct output.
- Deterministic — Same input, same output, every time. Critical for procedural generation where you need to regenerate the same layout from a seed.
Three iterations in one day sounds wasteful, but each one was informed by what the previous one got wrong. MapLayout tried to be too smart. GeometrySpec tried to be too general. Factory just builds what you tell it to build.
The key insight: geometry is cheap, abstraction is expensive. A room is four walls, a floor, and a ceiling. Don’t wrap that in three layers of indirection. Describe the intent simply and let the build pipeline handle the rest.