/ build-in-public / I Tune My Game's Balance By Editing JSON, Not Code
build-in-public 6 min read

I Tune My Game's Balance By Editing JSON, Not Code

All of World 11's balance lives in data files. Tuning the difficulty means editing JSON and rerunning, never touching the simulation. Here is why that split matters.

World 11 formation select screen showing the eleven formation options

When I am tuning World 11, I almost never open the simulation code. I open a data file, change a few numbers, run the game, and look at the result. The match engine, the part that actually decides who wins, sits untouched through nearly every balance change I have ever made. That separation is one of the best decisions in the whole project, and it is the thing I would tell any solo developer to set up early.

Play World 11 in your browser, or read the overview of how the whole game was built for the wider tour.

Where The Balance Actually Lives

World 11 keeps its balance in JSON, not in GDScript. There are three files that do the heavy lifting.

formations.json holds the eleven formations and the slots each one defines, from a 4-3-3 down to a 5-4-1. If I want to change what a formation is, I change this file. The code does not know what a 3-5-2 is. It knows how to read a list of slots out of a data file, and the data file decides what the formations are.

balance.json is the real control panel. The stages of the gauntlet live here, the six qualifiers, the four group matches and the points threshold you have to clear to advance, and the single-elimination knockouts. The rating weights live here, the chemistry rules for same-nation and same-decade picks live here, the number of rerolls you get lives here, and the expected-goals model that turns two team strengths into a scoreline lives here. Almost every lever that decides how hard the game is, how generous the draft is, and how a match resolves is a value in this one file.

nations.json holds the kit colors for each nation, which is more presentation than balance, but it follows the same idea. The look of a nation is data, not a hardcoded branch in the rendering code.

The point of all three is the same. The thing you would want to adjust is a value you can edit, not a line you have to reprogram.

Tuning Is Edit And Rerun, Never Edit And Rebuild The Engine

Because the levers are data, a tuning pass has a very short loop. I form a hypothesis, the group stage is letting too many weak teams through, so I open balance.json, raise the points threshold for advancing, and rerun. If the curve moves the way I wanted, I keep it. If it overcorrects, I nudge it back. The simulation code never enters the loop.

When balance is tangled into the engine, every tuning change is a code change, and every code change is a chance to break the thing that decides who wins. You go in to make the group stage slightly harder and you accidentally shift how a knockout resolves, because the values and the logic live in the same place and you touched both. Then you are debugging the match engine when all you wanted to do was move one number.

Keeping balance in data removes that whole class of accident. The expected-goals model reads its coefficients from balance.json. To make matches higher scoring I change the coefficients, not the model. The model is the same trustworthy code it was yesterday, fed different numbers. I can tune the feel of every scoreline in the game without ever risking the correctness of the simulation that produces them.

The Wall Between Engine And Content

What I have really built is a wall between engine and content, and the wall is what keeps the game tunable over months instead of for a week.

On one side of the wall is the engine. The simulation that runs a match and a gauntlet, the rating math, the loader that reads the squad data. This code is meant to be stable. It implements the rules. It does not implement any particular set of rules, just the machinery for whatever rules the data describes. I want this code to change rarely, because every change to it is a risk to the part of the game players trust to be fair.

On the other side is the content. The formations, the stage layout, the weights, the chemistry rules, the rerolls, the goal model coefficients, the kit colors. This is meant to change constantly. It is the part I expect to fiddle with for as long as the game is alive. Putting it in JSON makes that fiddling cheap and safe, because the engine reads content the same way no matter what the content says.

The reason this split is more than a tidy habit is that it lets two very different kinds of work proceed without colliding. Stable code stays stable because tuning never touches it. Volatile balance stays easy to change because it is just data. A change to the difficulty curve cannot introduce a bug in the match engine, because the change to the difficulty curve is not code at all.

Why It Kept The Curve Tunable Without Breaking Things

The payoff showed up most clearly when I was dialing in the difficulty toward a target, a strong well-built team winning the cup roughly a third of the time, which I measured with a balance bot covered in full in the post on balancing with a bot that plays a thousand games.

Hitting that number took many passes, raising a threshold here, reweighting chemistry there, adjusting the goal model so upsets happened at a believable rate. Every one of those passes was a data edit. I never once had to reach into the simulation to move the curve, which meant the simulation that produced my measurements was the same on the last pass as on the first. The dial I was reading stayed trustworthy precisely because the tuning never disturbed the instrument. If balance had lived in code, each adjustment would have been a code change underneath the very measurement I was using to judge it, and I would never have been sure whether the number moved because the game got harder or because I had introduced a bug.

The separation is what made convergence possible. I could chase one target across dozens of small edits, confident that the only thing changing between runs was the data I meant to change.

If you are building anything with knobs, and a game is nothing but knobs, pull every one of them out of your code and into data as early as you can. Decide what is engine and what is content, draw the wall hard, and never let a balance change become a code change. Your future self, three months and a hundred tuning passes in, will be grateful that the part deciding who wins has not been touched the entire time.

World 11 is the game these data files balance, and it is free to play in your browser. Spin a draft, run the gauntlet, and if a formation or a difficulty spike feels off, there is a feedback button right on the page. Play it at games.kevingabeci.com/world-11.

Built by Kevin

Like this? You'll like what I'm building too.

Two ways to support and get more of this work.

Desktop App

HEARTH

A privacy-first Life OS for your desktop. Journal, tasks, and notes that stay on your machine. Coming soon, direct download from this site.

Read more
Digital Products

MY TOOLKITS

Receipts-first toolkits for shipping after hours, building Claude agents, publishing on Amazon, and more. The exact methods I used, not theory.

Browse on Whop

Need This Built?

Kevin builds products solo, from first version to live. If you want something like this made, work with him.