The Rules That Built GrayHorizon
Before Gray Horizon had a world, an economy or a single player, it had a set of rules about what it was allowed to become.
Long before Gray Horizon became the browser game I am building today, I played eRepublik. I didn’t stay for very long, and I can’t say I understood much of it either, but it fascinated me. There was something very different about a game where the countries were filled with actual people. Politics existed because people wanted political power. Newspapers existed because somebody had something to say. Wars mattered because there was another group of players on the other side. Years later, I stumbled into that world again through WarEra. I saw a Reddit post from people from my country essentially calling for reinforcements.
They needed more people to join and help, and I thought: sure, why not? This time I stayed. And eventually I realized something slightly strange:
I didn’t really fall in love with WarEra. I fell in love with the people playing WarEra. (no homo)
The game gave us the map and most of the mechanics, but the community made it’s own rules about how it would govern the country. The arguments. The elections. The alliances. The newspapers. People taking things far more seriously than I thought possible, its only a game after all. That distinction eventually became one of the most important ideas behind Gray Horizon.
Gray Horizon existed before Gray Horizon The name itself actually predates the game I am making now. My first Gray Horizon was a single-player grand strategy project in Unity. It shared some of the same DNA: countries, geography, politics, strategy and a world that could change around the player. But it was fundamentally a different kind of game. WarEra made me reconsider that. The more I played, the more I found myself thinking about things I wished existed. Why does this system stop here? Why can’t players influence this? Why is this predetermined? Why isn’t geography more important? Why does the economy work like that? And, probably most dangerously for the amount of free time I would have over the following weeks: How would I do it? I’m one of those people who has a hard time seeing something that could be improved without immediately wanting to improve it. So the single-player Gray Horizon gradually stopped making sense to me. What interested me wasn’t simulating a society full of convincing computer-controlled people. It was giving real people enough power that they could build one themselves. That led to the current idea: modern Earth, and thousands of potential players entering it not as omniscient rulers, but simply as people, whether they choose to be themselves or take on an entirely different persona. it’s up to them, and I think that is the beauty of such games. That was the point where Gray Horizon stopped being a traditional grand strategy game. These ideas made me realize that these kinds of games aren’t traditional strategy games. Gray Horizon to me became something closer to a social platform with a geopolitical simulation wrapped around it.
And before I built it, I wanted rules. A complicated game gets rules whether you write them down or not. The dangerous part is letting all of them live only in your head. I already had most of Gray Horizon mentally designed, but “I know what I mean” isn’t very useful once a project starts becoming complicated. So I sat down and wrote a Game Design Document. And then I kept writing. The first version that entered the project repository was already 5,184 lines long, divided into 28 parts, 166 sections and three appendices. It contained 19 explicit Design Laws, an entire list of things that should not be built, and a ten-phase plan for turning the design into a functioning game.

In normal-document terms, it was well over a hundred pages. It took hours. And, looking back, writing it was probably one of the most valuable things I did for the project. My thought process was simple: I spend time writing and designing now, therefore I avoid a headache later, and I was right, it did help me. I never expected the GDD to predict the future. What I wanted was to force myself to answer the annoying questions early, before every answer turned into code.
There is a huge difference between: “The game will have politics.”
and: “What exactly is the player in this world?”
The second question changes everything about how you think games are made. So I tried to work from principles downward. You can think about the original design in roughly four layers:
Principles -> systems -> rules -> numbers
The further down that chain you go, the less sacred something should become. “The player is a citizen” is a principle. “Countries may have different government structures” is a system. “An election works in this particular way” is a rule. “An election lasts exactly X days” is a number.
If playtesting later tells me that X is terrible, changing it doesn’t cost me anything meaningful. It’s a simple change. If I suddenly decide that players should actually control entire countries from the sky, I’ve changed the game.
Rule 1: You are a citizen
The first design law was very simple: The player is a citizen first.
That sounds almost trivial until you follow it through. In Europa Universalis, you are France. You are the country itself. France’s treasury is your treasury. France’s armies are your armies. France’s diplomacy is your diplomacy. The state has one intelligence controlling it: you. Gray Horizon cannot work like that. If you are Serbian, Serbia is not you. You are one Serbian. Maybe you work in a factory. Maybe you own the factory. Maybe you write a newspaper complaining about the government. Maybe you join a political party and eventually become part of that government. Maybe you leave Serbia entirely. The important part is that none of those roles are guaranteed. The country belongs to the people currently participating in it. That one decision creates most of the game. You need employment because a citizen needs somewhere to work. You need companies because somebody has to employ them. You need journalism because citizens need a way to speak publicly. You need elections because somebody has to receive political authority. You need military organisations because a war cannot just be “the Serbia player clicked Attack.”
A lot of Gray Horizon’s complexity isn’t there because I wanted a giant feature checklist. It follows from asking: If the player is really one person inside this world, who actually does this? That is a useful game-design trick in general. A good foundational rule should generate consequences. If a “design principle” never forces you to make a difficult decision later, it probably isn’t much of a principle. The community is the content This is probably the idea I care about most. I don’t think a game like Gray Horizon survives because I can continuously manufacture enough content for everyone. I couldn’t. No developer could. Instead, the systems have to make players interesting to one another. That is why I sometimes think about Gray Horizon more like a social platform than a traditional game.
Not because I want to build Facebook with tanks. Please, no.
I mean it in the sense that Facebook doesn’t have to design the conversation you’re going to have tomorrow. It gives people structures for forming relationships and communicating, and the people provide the content. Gray Horizon needs to work in a similar way. I can create elections. I cannot create a political rivalry. I can create newspapers. I cannot create a scandal. I can create military units. I cannot create the group of people who become famous for fighting together. I can create diplomacy. I cannot write the history of two countries that spend six months alternately fighting and cooperating with each other. That part belongs to the players. Which means that a mechanic is most valuable when it gives people another meaningful reason to interact. This also gives me a useful filter when deciding whether a feature deserves to exist. Does this create an interesting decision? Does it connect players? Does it create something other players can react to?
If not, maybe it is technically impressive but socially dead. That distinction has already killed more than a few ideas.
Rule: money cannot buy power
There was another rule I considered non-negotiable from the beginning: Gray Horizon cannot be pay-to-win. This matters even more in a social game than it does in a normal multiplayer game. I couldn’t imagine spending weeks, months even, grinding only to be beaten because the enemy has deeper pockets. Personally, it infuriates me. Don’t get me wrong, plenty of games outside this niche do it, and quite successfully, but I care about this game.
So then you ask: how does the game actually make money? By offering you simple incentives to do so, only if you want to, the game isn’t a gacha and it never will be, it will not force your hand, ever. This is my philosophy.
That is partly a monetisation decision, but it’s also a simulation rule. Once real money can manufacture in-world power, every other economic decision I make about making the game balanced and fair becomes fake, because people literally spawn money out of thin air, that is not fair, even less balanced.
The map isn’t decoration
Another original design law said that the map is the primary interface.
Greatly inspired by how Paradox games work, the map being the main interface the players interact with, came very naturally to me. I really couldn’t imagine it any other way.
I know this may be a point of contention for some people, because they absolutely don’t like looking at maps, but I do, I’m even half convinced to go back to regular maps instead of GPS for navigation, if only I weren’t lazy for modern convenience :D
It’s extremely easy to get this wrong. If you approach a browser game like a normal web project, you end up building a SaaS website and calling it a game. It doesn’t work like that. This is a game that works in your browser, and as a developer, you have to treat it like that. Pretty soon you have a perfectly competent web application with a map buried inside it. Gray Horizon would later fall into exactly that trap anyway. More than once.
But having the rule written down gave me something important to argue against myself.
This may work, but is it still the game I intended to make?
We will get back to that in another devlog.


Sometimes good design starts with “no” One of my favourite parts of the original GDD wasn’t the feature list. It was the Do-Not-Build list. Before deciding everything Gray Horizon needed, I also wrote down things I specifically did not want. The early document explicitly ruled out ideas including national currencies, delivery timers, RPG-style character stats and abstract “diplomacy mana.” It even ruled out unnecessary infrastructure complexity such as jumping to microservices and Kubernetes just because the project might someday become large.
Some of those deserve explanation.
Let’s take Diplomacy mana for example.
A lot of strategy games need abstractions because one person controls an entire state and the game has to limit how many things that person can do. So perhaps signing a treaty costs 50 Diplomatic Influence. Perfectly reasonable for that kind of game.
It would make very little sense in Gray Horizon. If Serbia wants a treaty with Greece, the interesting constraint is not whether Serbia has accumulated enough blue diplomacy points. The constraint should be that Greece has players on the other side who have to agree. The difficulty comes from other people sitting on the other side of the same decision. That, to me, is much more interesting.
Or consider delivery timers. A market can make geography matter by forcing somebody to wait twelve real hours for their purchase to arrive. But waiting isn’t necessarily strategy. If geography can instead affect shipping cost, available routes, tariffs, blockades and access to markets, then distance still matters without turning the game into a parcel tracking simulator, which most of us do in real life, and no one finds it particularly interesting.
The distinction I kept coming back to was: Does this create a decision, or merely a delay? That question is useful.
How that works in the game today
When you buy something on the market, it’s yours as soon as the trade goes through. You don’t wait for anything.
Distance still matters, it just shows up in the price. For every seller, the game works out what their goods would cost you delivered to where you are: their price, the freight along the cheapest route, your country’s import tariff if the goods cross a border, and a small market fee. Your order gets filled by whoever comes out cheapest after all that, and that isn’t always whoever has the lowest price tag.
Say you want 100 iron ore. One seller is 200 km away and asks 4.00 credits each, another is 2,500 km away and asks 3.80. Freight on raw goods is 0.12 credits per unit for every 1,000 km, so the nearby ore ends up costing you about 4.02 and the “cheaper” one about 4.10 (both also pay the same half a percent market fee). So you buy from your neighbour, and you never had to work any of that out yourself.
The freight bit is short enough that I can just show you the game’s code for it. Quantity times weight times distance times a rate, and it never goes below one cent:
logisticsFee, packages/game-rules/src/economy.ts
return Math.max(
1,
Math.round(
(quantity *
unitWeightMilli *
effectiveRouteKm *
BALANCE.economy.logisticsRatePerMilliWeightKm) /
1_000_000,
),
);
Politics gets into this too. Countries set their own tariffs by law and can vote in an embargo against another country, and shipping goods through occupied provinces costs more. So a war can hurt trade without any delivery timers, because the routes themselves get worse.
And then there are decisions like “do not start with microservices.” It’s engineering restraint. A system does not become professional because it contains more moving parts. Complexity has to buy something. At the start of a project, Kubernetes would have bought me approximately nothing and charged interest. So it went on the list. A GDD (Game Design Document) should constrain you, not imprison you. There is a danger in writing a document this detailed. Eventually you can start treating your own guesses as the Bible. I never expected Gray Horizon to be built exactly as written. It couldn’t be. Before players exist, you don’t know what players will do. Before an economy exists, you don’t know whether its prices make sense. Before the interface exists, you don’t know which apparently obvious control will confuse everyone. Before a war exists, you don’t know whether starting one is too cheap, too expensive or simply boring. You can only make assumptions. You’ll be wrong about plenty of them, but they should at least be educated assumptions. But still assumptions.
There are two very easy ways to screw this up. One is to write nothing down and tell yourself you’ll figure it out while coding. Then every system gets designed in isolation and eventually they start contradicting each other. The other is worse in a different way: treating the GDD like scripture. If the document says something is fun and the game says it isn’t, the document loses.
And the fact that I was wrong, or rather let’s say the GDD was wrong, only strengthens my opinion. The original world design had roughly 1,250 land provinces. The document even worked through Serbia as an example with seven provinces.
On paper, 1,250 seemed logical. You want the game to have as much detail as possible, right?
And yet, you’d be wrong. A geopolitical strategy game needs to function like a game, not a geography atlas that they make you look at in school. It is extremely easy to fall into this pitfall, you start treating the game as an atlas just containing a game by chance, and the players go away as soon as they arrive. Still, let’s go back to my original example… Too few and conquering one region can mean taking half a country.
So: around 1,250. Still reasonable, right? Then I actually built the world. The first Earth map hit exactly 1,250 provinces. That sounds like success. But it wasn’t.
Real geography, and more importantly administrative geography, was used to divide each country. Roughly, it just made sense, but it was so..so wrong for the game that I had envisioned.
You might ask why I built it like that in the first place. It made sense at the time, and honestly a lot of it still does.
The world in Gray Horizon is permanent. A province that exists today has to still be the same province a year from now, because ownership, wars and history all point at it. The easiest way to guarantee that is to not draw the map by hand at all: take a public map anyone can check, run the same steps on it, and you get the exact same provinces every time. Official borders seemed like a good place to start, since they’re real and somebody has already agreed on them. On top of that, it gave me the whole planet in a few hours, with exactly the number the design document wanted.
The notes that came with that first map even said it wasn’t done yet, and that the shapes, names and borders still needed a proper human look before a real world could use them. That look is where it all fell apart.
So what did those steps actually do? The whole method fits in one sentence, which I find kind of funny now.
I like to imagine it as the colonisers drawing the borders in Africa: they divided it all neatly, they probably used rulers, it’s all neat. But is it functional? Hell no, they did not care at all about who lived there or what languages they spoke. They did the borders similarly to how we did it, mechanically. At no point did the guy drawing the borders know or care what a place was called, or whether anyone would ever want to fight over it.
The number on each note came from land area, toned down a bit so that a country a hundred times bigger doesn’t get a hundred times more provinces. Russia came in as 87 pieces and got to keep 78. France came in as 122 pieces and got 9.
Quick note before the code, because it’s easy to get the wrong idea: Gray Horizon isn’t written in Python, the game itself is TypeScript. This was a separate tool that builds the map once, offline, before the game ever touches it, and Python just happens to have the geometry libraries I needed.
If you read code, this is the colonizer with a ruler:
Step 5: tools/world-data/generate_earth.py, lines 177–197
def merge_regions(regions: list[Region], target: int) -> list[Region]:
build_neighbors(regions)
active_count = len(regions)
while active_count > target:
source_index = min(
(index for index, region in enumerate(regions) if region.active),
key=lambda index: (regions[index].area_sq_km, regions[index].source_keys),
)
source = regions[source_index]
candidates = [index for index in source.neighbors if regions[index].active]
if not candidates:
candidates = [index for index, region in enumerate(regions) if region.active and index != source_index]
target_index = min(
candidates,
key=lambda index: (
-source.geometry.boundary.intersection(regions[index].geometry.boundary).length,
geometry_distance(source.geometry, regions[index].geometry),
regions[index].area_sq_km,
),
)
destination = regions[target_index]
In plain English: as long as the country has more pieces than it’s allowed, take the smallest one, find the neighbour it shares the longest border with, and glue them together. The two lines starting with if not candidates are there for islands, so a piece with no land neighbours just gets glued to whatever is closest.
Some countries were cut into far too many pieces. Others had regions that made sense to a statistical office but not to a player looking at a strategy map. Hundreds of names literally ended in “Region.” We therefore reached the number that our “Bible” dictated, and yet, the number was wildly wrong. Over the next roughly day and a half, the world went from 1,250 provinces to 948, then 847, then 741 as the approach changed from “make source geography fit the target” toward deliberately designing a playable board. Because again, you have to treat the map the same way you’d treat a game board. This is one big D&D session, only on real Earth.


If you’re wondering how London ended up in a province called Oxfordshire, I re-ran the original script and followed it along. Britain came in as 277 pieces and was allowed 6, so there were 271 glue steps. This is what happened to London:
- Camden got glued onto Westminster (step 34).
- Westminster, with Camden already in it, went onto Ealing (step 68).
- Ealing went onto Surrey (step 188).
- Surrey went onto Oxfordshire (step 255).
Oxfordshire was never the smallest piece left, so it never got picked up, and it kept its name. That’s pretty much the whole naming rule: a province made of one piece keeps that piece’s name, and anything glued together gets the name of the piece everything else was glued onto, with “Region” stuck on the end:
def region_name(region: Region, country_name: str, ordinal: int) -> str:
unique_names = list(dict.fromkeys(name for name in region.names if name and name != "-99"))
if len(unique_names) == 1:
return unique_names[0]
if unique_names:
return f"{unique_names[0]} Region"
return country_name if ordinal == 1 else f"{country_name} Region {ordinal}"
Edinburgh went the same way. It went into Midlothian, then into the Scottish Borders, and then in step 271, the very last one for Britain, into North Yorkshire. The two British bases on Cyprus didn’t have any land neighbours, so they went to the closest region, which turned out to be Kent, and Kent later went into Oxfordshire. So yes, a province named after an English county had a piece of Cyprus in it.
This was the only check the script ever did on its finished work:
if len(output_provinces) != TARGET_PROVINCES:
raise RuntimeError(f"Expected {TARGET_PROVINCES} provinces, generated {len(output_provinces)}")
If it isn’t exactly 1,250, stop. The game’s own test (in TypeScript, like the rest of the game) checked the same thing on the map file:
packages/database/src/earth-dataset.test.ts
const countryIds = new Set(dataset.countries.map(({ id }) => id));
const provinceIds = new Set(dataset.provinces.map(({ id }) => id));
expect(countryIds.size).toBe(213);
expect(provinceIds.size).toBe(1_250);
And not long after that, I removed the global province-count target completely. That became one of the first major lessons of the project: Some numbers should be outputs, not goals. If I decide beforehand that Earth must contain exactly 1,250 provinces, every country becomes a contributor toward satisfying my spreadsheet.
If instead I ask how many provinces Spain needs, how many China needs, how many Serbia needs, how many Switzerland needs, and whether each division creates an interesting piece of geography to play with, then the final number is simply whatever those answers add up to. This sounds so obvious to me now. It did not sound obvious before I had a terrible map proving it.





The point of the rules
Fast forward to a month of development, and plenty of things are different from the original GDD claimed. And that’s perfectly okay.
But the things I actually cared about were still there. You are still a citizen rather than a country. The map is still supposed to be the center of the experience. Players still create the politics, companies, newspapers and conflicts that matter. Paying real money still isn’t supposed to make you stronger. And when a detailed mechanic collides with one of those principles, the mechanic is the thing that has to move. That’s what the GDD ended up being useful for. It told me which things I could happily throw away and which ones would mean I’d started making a different game. It’s a way of deciding which parts of your idea you’re willing to negotiate. I changed the province count, and I happily would change it again. I changed economic assumptions. I changed things I had written with complete confidence earlier. But if Gray Horizon ever becomes a game where the most important participant in a country isn’t its community, or where the map is irrelevant, or where the person who pays the most has the most power, then the problem isn’t balance anymore. I’ve built the wrong game. Shame on me. The first set of rules exists to make that harder to do. As you’ve probably guessed, writing was the easy part. Seeing how it all fits together is the hard part…
More on that in the next devlog. Thanks for reading!