Learning By Building: Fantasy World Generators
Week two of writing while traveling! Formatting is really difficult, and I have to write in details from memory, so please excuse the small fuzzy details here and there. In continuing with learning how complex things work by building, this time it is a fun game project that has been bouncing in my head for a while. The gist is a program that simulates a fantasy world across long time with kingdoms coming into and out of existence and characters that go about life and leave a trace in the world for the next generation. The background starts with the game that probably does this more completely than anything else… Dwarf Fortress. I've always been a fan of browsing through the various stories that players would tell about how their fort started, evolved, struggled, and often spiraled into chaos and destruction. Those tales were told based off players controlling and watching their small colony of dwarves evolve over time. They then filtered those details through their personal storytelling skills which resulted in the epic tales that last on the Internet. I don't have a lot of interest in learning to actually play dwarf fortress due to time constraints. But I loooove automata and complex emergent situations that arise from many layers of simulated rules interacting. So my current experiment is cobbling together a simulation system that can provide the base world and interactions for such a simulation. Then, assuming I can get that far, I want to populate the world with “characters” that live simulated lives. Hopefully if everything works together, those characters will do interesting things that someone reviewing the system behavior can pull an interesting narrative out of.
Big rock issues

The primary design goal of this project is this: simulate a fantasy world, have characters with personality traits and motivations, then have all these characters and world interact, leaving a rich history of interactions and events that can be analyzed and interpreted into a story by humans browsing the log with tools later. I also had a few quirks that I wanted. The world is “roughly the size of Europe” so plenty big, but on a cylinder world where east/West loop but north/south doesn't I want this thing to run for long periods to cover thousands of years of “history’ This means civilizations should come and go, climate can shift and change and all of those effects like ruins remain for future characters to find.
Language considerations
When plotting this thing, given that these simulations can quickly balloon into thousands of objects interacting with each other, picking a language and overall architecture that can handle all that work without bogging down was important. While I'm naturally a Python person in many cases, it didn't seem like the right language for the job. The GIL isn't great for this kind of game loop. Plus I've had tons of my own analysis programs start breaking down at a couple of gigabytes of live memory use. In the end I went with C#, plus a sqlite database as the actual “history recorder”.
Action semantics
Designing this world demands we make design decisions that are inherently flexible. This sort of simulation inherently demands more and more complexity as new objects, concepts, and actions are added to the game. To keep things sane and extensible the idea is to have all things that take action fire off an event with a standardized interface (essentially actor-verb-object) to an event queue. Then in the next phase of simulation all the results like damage or state changes are calculated and then logged. This way with some abstraction a lot of objects and actions can be made off the same pattern. Trying to model everything using function signatures or elaborate interface contracts easily looked like a maintenance disaster in the making.
Creating a world of your own design
So if you ever thought designing the interface cli/UI bits of a data pipeline was a lot of design, making up the specs for your own world is a lot more involved. Much of it has no actual right or wrong answer, so the only thing you can have is personal taste and preference. And there are so many details to glue down even if you let an LLM fill in a lot of the gaps.
For example, you have a planet and want it to be semi realistic in terms of geography. You want some oceans, lakes and rivers. You want some places that have mountains, forests, grasslands, tundra, and a bunch of other biomes. But how do you distribute them across the planet in a believable way and not just “at random”?
The answer I eventually landed on was to simulate a world from the ground up. What I mean is to generate a map of tectonic plates and then attribute fault lines between the plates where there could be volcanic activity or realistic places to put mountains. The rest of the world has a seeded random height map that's tuned for a balance of smoothness and variation. Then once all the heights are set, we can normalize and fill the oceans up to a certain elevation. That way, if we want to simulate climate change effects over the long term we can just raise/lower the sea level. Rivers are also simulated start high but flow and meander down according to geography.
Then the world needs a very rough climate model. At the very least we need to figure out where there is rain and moisture (basically places near bodies of water). On top of that the planet has a simple model of the trade wind bands like we have on the earth. Those wind bands blow east/west based on the latitude and carry water deep into the continents, unless a mountain gets in the way.
We also need a simple temperature model for the world, and that means simulating the highs and lows of the four seasons. This is primarily driven by latitude and we can just quickly draw inspiration from temperature values at different real world locations.
Only when we have these things: basic geology/topography, available moisture, and overall temperature, can we then attribute biomes to a patch of land. Another benefit of this approach is that if climate parameters change, we can reassign the biome to simulate climate drift. There's of course a bunch of edge cases that need to be handled as we get into the weeds but it is a workable framework.

Gridding up the world
I had specified the planet map to be“Europe sized” as a rough target for scaling everything else. The world is then divided up into squares that are 10km on a side and that's how all the biomes and general parameters are set. The point is that we simulate the general world at this zoomed out scale. The goal is to zoom into these squares and somehow interact with them like you would in a 2d-like Minecraft game or something. I dunno. Completely TBD. We are so far away from that point right now.
Characters
Once we have a working stage for characters to stand on, the next hard part is figuring out how to populate the world with living things that take actions.
The basic premise of all characters is that they exist in the game as different tiers based on how important they are and how much we record their information.
There’s the tier1 characters. These would be considered “main characters” in a book. Kings and queens, very notable merchants or adventurers, even legendary creatures like a dragon. These get access to most actions and the event recorder is set to record more details about whatever actions they are taking. They have a character stat sheet that powers their agent based model and behavior.
Next there are tiwr2 characters. These are actual simulated entities that have a stat sheet much like the tier1 characters. These are supposed to be notable characters that generally have a supporting role. This may. Be a general that is delegated to lead an invasion, or a notable artisan or townsperson. These folks can take actions that are recorded but they dont get as much attention. They're there to support the story or help the main characters through delegated tasks. On occasion these characters may get promoted to become tier1 if we need it, like if the king does and we promote a replacement
Finally there are tier3 characters. These are the general population of the game. They don't actually have a character sheet, not any physical presence in the world. They're just modeled as collective statistics in the background. They generate food and supplies that the higher tier characters can consume. Tier3 population also determines the chances of a tier2 character spawning. The population can get affected by mechanics you would expect, growth, stress, disease, war, wildlife attacks.

The hardest part – Balance
While I could get Claude to help me do the extremely heavy lifting of coding these mechanics into a coherent piece of software that runs, there is one thing it can't do – balance the game mechanics into something that is interesting for us to observe.
A lot of the game is tuned via configurable parameters. For example, what is the probability of disease springing up in city? How quickly and far does it spread? How deadly is it? Does it have any other impacts? If the numbers are tuned too far in any direction, the game gets boring – everyone lives or everyone dies. But in theory, there is a sweet spot where sometimes disease is a problem, and other times it is not.
The same goes for other mechanics like the balance of food production, city growth, and “things that make populations go down”. There's lots of inputs to the system and you have to tweak the knobs to figure out what feels correct.
In one version of the game, I ran the sim for. 10k years and eventually the entire north/south tundra became dotted with the ruins of villages… because it was essentially impossible to sustain a population up there without luck, but the game had a “if there's not enough settlements left spawn some randomly on the map”. These two rules essentially kept feeding random groups of villagers to the ice fields.
Another really hard to balance thing is just cities in general… how big should they get? Should there be empires spanning a continent? How can we encourage that with the existing rules and mechanics that we have without forcing things down a specific path? One version wound up having cities become giant blobs that slowly ate the landscape. The current version can't seem to make civilizations that have more than 2 cities because they either fail to trigger the event needed to establish a new one, or anything they establish dies.
So having interesting mechanics is one thing, but getting the whole thing functioning as a working system is the hardest part of the project. While it's easy to predict the overall arc of a given change, like nudging the disease lethality rate will obviously kill off populations faster, it's hard to predict how it will interact with all the other systems. Very often it spirals into a boring or pathological state.
Then to make things even messier, I try to balance the system whenever we get to a new feature checkpoint. But since we are still a long ways off from including all the mechanics and interactions I want, I inevitably disrupt the delicate equilibrium state I found through testing. But at the same time, I can't save the play testing to the end because I need to verify that new mechanics are functioning as intended. It's an endless cycle of building a house of cards and knocking it down again.
But despite all this, hacking this thing together has been fascinating. It's stretching all sorts of mental modeling muscles as I try to juggle an increasingly complex web of relationships to achieve some personal perception of balance that may not actually exist.
For now, for those who want to follow along and play with the code, feel free to mess with the repo. The whole project is still a long way from done in any real sense, but it does function.
https://github.com/randyau/worldgen
Standing offer: If you created something and would like me to review or share it w/ the data community — just email me by replying to the newsletter emails.
Guest posts: If you’re interested in writing something, a data-related post to either show off work, share an experience, or want help coming up with a topic, please contact me. You don’t need any special credentials or credibility to do so.
"Data People Writing Stuff" webring: Welcomes anyone with a personal site/blog/newsletter/book/etc that is relevant to the data community.
Counting Stuff Official Forums: Discuss posts, or other data topics with the community.
About this newsletter
I’m Randy Au, Quantitative UX researcher, former data analyst, and general-purpose data and tech nerd. Counting Stuff is a weekly newsletter about the less-than-sexy aspects of data science, UX research and tech. With some excursions into other fun topics.
All photos/drawings used are taken/created by Randy unless otherwise credited.
Supporting the newsletter
All Tuesday posts to Counting Stuff are always free. The newsletter is self hosted. Support from subscribers is what makes everything possible. If you love the content, consider doing any of the following ways to support the newsletter:
- Consider a paid subscription – the self-hosted server/email infra is 100% funded via subscriptions, get access to the subscriber's area in the top nav of the site too
- Send a one time tip (feel free to change the amount)
- Join the Approaching Significance Discord — where data folk hang out and can talk a bit about data, and a bit about everything else. Randy moderates the discord. We keep a chill vibe.
- Get merch! If shirts and stickers are more your style — There’s a survivorship bias shirt!