Games that generate their worlds randomly have the problem that the generation should probably be deterministic. Meaning, using the same random seed value should always make the same world. This means seed codes can be shared with friends and they would all get to play the same world together. This seems like a good thing. But what happens when I add a new feature to how worlds are generated, or I fix a problem with how they are generated that was causing them to be unbeatable? In these situations, the seed code would have to generate a different world than expected because the code doing that work has changed. This seems like a bad thing.

This is where the concept of internal versioning comes in. If I’ve changed how the generator that makes the world you play in works, I essentially need to keep the old way around too. If I pair that with something in the seed code to indicate which version it is, that should theoretically work. In other words, if you have a code for version 1 and the game is on version 2, it should generate a world the same way it did for version 1. Old codes that made broken worlds would still do so, and problems that existed in older versions would still be around even if I’ve fixed them in a newer version. It sounds like a good solution, but what is really involved in making this work?

I’ve spent the last two weeks working on the save system for Fracterebus. Nearly all of that time was spent trying to come up with “versionable patterns” for it. Weaving in various interfaces, splitting namespaces across logical version boundaries, generifying data access using things like dictionaries, and coming up with ways to store data that I can’t know anything about before digging out a version indicator. As I wandered deeper and deeper down this rabbit hole that even Alice would be proud of, I realized that crafting a way to handle version determinism was going to shred every single part of the game’s code. It just has its hands in everything, and everywhere it touches ends up touching other things that need those hands too. It quickly becomes something that drives everything in the game and adds a ton of overhead to things that would otherwise be pretty straightforward to code.

Then I had a sort of realization. I’d devoted two weeks to make my code internally versionable in one relatively small system. Worse, I wasn’t satisfied it would actually work in the long run. On top of that, I still have about 98% of the game left to write. If every part has this same amount of massive complexity and the delays that come with trying to support versioning, I will never make any meaningful progress. This situation is compounded by the fact that the version supporting patterns are frustrating and confusing for me. This seems to be back to being a bad thing again.

There are really two approaches here. I could do all the versioning things, or I could not do all the versioning things. To try to figure out which approach I should take, I did some math. I decided to use numbers that are so optimistic that they brush up against ridiculous so I could define a sort of worst case scenario. I needed to think about what I was doing, the road it was putting me on, and what sort of impact my decisions would have. Here’s what I came up with.

First, let’s assume I manage to sell 10 million copies of the game, total, over the whole development life of the project. (That would be amazing.) Next, we’ll say that 50% of the people that bought the game will beat the initial world that everyone plays. (Based on Sony trophy percentages, this guess is probably 2 to 4 times more than will actually happen.) Next, we’ll say that 50% of the players that beat the game want to immediately play it again. (Again, based on things like New Game+ trophies, this is a massive stretch.) We’ll also say that 100% of the people that received a code from someone already own the game and that they actually use that code to play it again. (Because everyone obviously has friends that would do that.) These numbers should be so far out in left field that they comfortably cover the fans and speed runners that generate and exchange piles of codes.

There are some amusing caveats to this logic puzzle. Worlds are generated up front when you start a new game and are included in the save data. That means a version change that modifies how generation works would have no effect on any games in progress. Anyone starting a game based on a new seed code, even one previously seen by someone else they don’t know, would lack the context needed to know that there is anything different about what is generated. That’s even true if multiple people share a code with the intention of exploring it together at the same time. In other words, a lot of the codes shared between people are unlikely to be received by someone with the context to recognize differences. For the sake of this math being ridiculous, we will assume that 100% of codes used to generate worlds for any reason have this context anyway.

Fracterebus currently has a seed code with 42 bits of entropy that can meaningfully affect map generation. That means there are 4,398,046,500,000 (roughly 4.4 trillion) possible worlds it can generate. Using the numbers above there will be 2.5 million codes out there in the world that people have seen and have expectations about the contents of. That means that in the life of the game, roughly 0.0000568% of the possible worlds will have been seen where someone can be slightly annoyed that they are different than expected. The remaining 99.9999% (and change) of worlds that are possible will have never been seen by anyone at all and will have no effect on the player population.

This has led me to the singular conclusion that spending huge amounts of time jumping through hoops to support deterministic seed codes that will dramatically increase the complexity code… Well, it simply isn’t worth it at all.

Thus, my approach will simply be that patch notes will always include the following line:

  • Newly generated worlds from known seed codes may be different than expected.

Problem solved.