/ game-development / Tune Data, Not Code: Keeping Game Balance in JSON
game-development 8 min read

Tune Data, Not Code: Keeping Game Balance in JSON

Both of my games keep their content and balance in JSON files, not GDScript. Here is why pushing formations, ratings, economy, and difficulty into data made them faster to tune and safer to change.

Tune Data, Not Code: Keeping Game Balance in JSON - Complete game-development guide and tutorial

I build games on nights and weekends, which means I get maybe ninety minutes at a stretch before real life pulls me away. That constraint changed how I write games more than any tutorial ever did. If a balance change costs me a recompile, a scene reload, and three minutes of clicking back to the moment I want to test, I will simply not make the change. So I stopped putting balance in code. Both of my Godot games keep their content and their tuning knobs in JSON files that load at runtime, and the game logic just reads them.

The two games are different on the surface. World 11 is a World Cup draft roguelike where you spin a nation and a year, draft eleven real players, and survive a fourteen match gauntlet. Orchard Deck is a cozy collectathon, all cards and slots and a slow economy that opens up over a few hours. They have almost nothing in common as experiences. They have nearly everything in common in how they are structured, because both of them follow one rule. Tune data, not code.

What Actually Lives in the Data

In World 11, the JSON files carry the formations, the squads and their per player ratings, the gauntlet stage list, the chemistry rules, and the entire match balance model. That last one matters most. Shot rates, conversion percentages, the difficulty ramp across the gauntlet, and how much random variance a single match carries are all numbers in a file, not branches in a function. The simulation reads them and plays out a result.

Orchard Deck is the same shape. The cards, the slot tech tree that gates what you can build, and the economy that decides how fast resources flow all sit in data. When I want a card to feel rarer or a slot to unlock later, I am editing a value, not rewriting the unlock logic.

The GDScript that runs both games is deliberately dumb. It knows how to read a config, apply the numbers, and produce an outcome. It does not know whether the gauntlet should be hard or easy. That decision lives outside it.

Balancing Becomes Editing a Number

Here is the practical payoff. When the World 11 gauntlet was too easy in an early build, I did not open the match simulation. I opened the balance file. I nudged conversion rates down, pushed the difficulty ramp up across the later stages, and re ran my test. The simulation code never changed. Not one line.

That is the whole thesis in one sentence. A balance pass is a sequence of edits to numbers, and numbers belong in data. The moment balance lives in code, every balance pass is a code pass, and every code pass carries the risk that you broke something while you were just trying to make the final match a little meaner.

Two Brains, Two Passes

There is a quieter benefit that took me a while to notice. When the tuning lives in data, the designer brain and the programmer brain stop fighting over the same file.

A programmer pass is about correctness. Does the simulation resolve a match without crashing, does chemistry apply in the right order, does the economy never go negative. A designer pass is about feel. Is the gauntlet tense, does drafting a strong eleven feel rewarding, does the economy open up at a satisfying pace. Those are different questions and they want different headspaces.

When the knobs sit in JSON, I can put on the designer hat for an evening and never read a function signature. I am reading and writing values that map directly to feel. The next night I can put on the programmer hat and harden the simulation without second guessing the balance, because the balance is not in there.

The Headless Harness Reads the Same Data

This is where data driven design stops being tidy and starts being powerful. Because the balance lives in a file, I can write a headless test harness that loads the exact same file the game loads, runs the simulation hundreds of times with no rendering, and reports the outcomes.

For World 11 that means I can ask a direct question. Across five hundred simulated gauntlet runs with the current numbers, what is the win rate. The harness runs in a few seconds because there is no window, no animation, no input. It just feeds the data through the same simulation the player gets and counts how often you survive.

That feedback loop is the thing I would not give up. I change a number, I run the harness, I read a win rate, I change the number again. A full tuning cycle that would have been a multi minute round trip through the running game collapses into seconds.

A Concrete Tune From Too Easy to One in Three

The clearest example is the World 11 gauntlet difficulty. An early version was too easy. Winning the whole fourteen match run felt like a formality, which kills a roguelike, because the genre lives on the tension of a run you might lose.

I wanted a roughly one in three win rate. Hard enough to sting, fair enough to chase. Getting there was entirely a data exercise. I opened the balance file, adjusted the difficulty ramp and the conversion and variance numbers, and ran the harness. Too punishing. Adjusted again, ran again. Still off. A handful of iterations later the harness reported a win rate hovering around one in three, and I stopped.

At no point did I open the simulation. The match engine that produced those five hundred results was byte for byte identical from the first iteration to the last. The only thing that moved was the numbers, and the harness told me, in seconds each time, exactly what those numbers did to the game.

Content Scales Without Touching Code

The other thing data driven design buys you is cheap content. Adding a squad to World 11 is a data edit. The roster, the ratings, the formation it favors, all of it is JSON. The drafting and simulation code does not grow by a line, because it was never written to know about any specific squad. It was written to read whatever squads the data hands it.

Orchard Deck is the same. A new card is a new entry. The slot tech tree, the rarity, the economy hooks, all data. I am not extending the engine when I add content. I am extending the content, and the engine does not care how much of it there is.

This is what keeps a solo project moving. The expensive part of a game is the systems, and you write those once. After that, growth should be additive and boring, and the only way to make it boring is to keep the things you add often, content and balance, out of the code you change rarely.

The Pure Simulation Stays Small

Pushing every knob into data has a side effect I love. The simulation itself stays tiny. Because none of the tuning lives inside it, the World 11 match engine is a small, readable function that takes data in and produces a result out. It has no opinions baked in. Every opinion is a number it was handed.

A small pure core is easier to trust, easier to test, and easier to come back to after a week away. When I open it I am not wading through a thicket of magic constants and special cases. The special cases are in the data, where I can see them all in one place and reason about them as a set.

The Tradeoff Nobody Mentions

I would be lying if I sold this as free. The cost of moving everything into data is that data can be wrong in ways code cannot. A typo in JSON, a missing field, a string where a number should be, a squad that references a formation that no longer exists. The compiler will not catch any of that, because to the compiler it is just text in a file.

The fix is validation. I run a smoke test that loads every data file and checks it for the obvious failures before I ever trust a build. Does every squad have eleven players. Does every card reference a slot that exists. Are the numbers actually numbers. Schema drift is real, and the more content you add the more surface there is for it. If you go data driven, you have to pay for it with a validator that fails loudly the moment the data goes malformed. That smoke test is not optional. It is the price of the whole approach, and it is well worth paying.

Why I Will Keep Doing It This Way

Every game I have built as a solo dev pushes more into data, not less. The pattern earns its keep on the first tuning night. Balance becomes editing numbers, the designer pass and the programmer pass stop colliding, a headless harness measures changes in seconds, content grows without code churn, and the simulation at the center stays small enough to trust. The only tax is a validator, and that is a fine trade.

Both games are free to play in your browser. If you want to feel what a one in three gauntlet plays like, or build a slow card economy in a cozy collectathon, you can play both at games.kevingabeci.com.

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.