Field Notes

The Real Cost of a Gacha Banner

A real project story: I designed a full-collection gacha system for an ARPG studio, then used simulation, not guesswork, to prove what it would actually cost players before it shipped.

The studio came to me with a clear brief for their ARPG. They didn't want the usual single-item banner where you chase one rare pull. They wanted players to collect a whole set, rarer items, mid-tier items, and smaller collectibles, and they wanted it to feel new and exciting, not a copy of the standard pattern. My job was to design the whole system from the ground up: what's in the pool, the odds, the price, and a few ideas of my own to protect players from bad luck along the way.

Designing it, then proving it

Once I had a design I believed in, I didn't want to just hand it over as a recommendation and hope it held up. I needed to prove, before anyone else saw it, what it would actually cost a player to finish it.

The system had a few moving parts. Every pull landed in one of the item pools, or triggered a lucky pull instead. On a lucky pull, a pity mechanic pushed toward the rarest items specifically. Every 25 pulls, if a player was still missing one of the rarest items, they got one for free, no luck involved. And every duplicate a player pulled turned into a currency they could save up and eventually spend to just buy whatever they had left.

Good on paper. But paper doesn't tell you what any of that costs a real player, in real money. So instead of relying on the average, I built a Monte Carlo simulation. Instead of calculating one number, you get a computer to run the banner thousands of times, using the real rules, and you look at what actually happens across a large group of simulated players.

What the simulation confirmed

Here is the part that mattered most.

Without any of those protections, a lucky player finished the whole collection for around €520. An unlucky player, purely on bad luck, could pay over €2,000 for the exact same collection. That's not a small gap. That's the kind of number that gets a game a very bad reputation.

Add the pity mechanic, the guaranteed catch-up every 25 pulls, and the option to buy back the remainder with saved-up currency, and that same unlucky player now pays about €80. The lucky player pays about €67. The whole range, floor to ceiling, shrinks from a gap of thousands of euros down to about thirteen.

That's the real value of designing it this way. Not that the banner became more generous on average, the price per pull never changed, but that no player, however unlucky, was ever left paying wildly more than anyone else for the same thing.

Giving the studio the wheel

I was confident in the design, backed by the simulation. But I did not just hand over a finished system and ask the studio to trust me.

I built them a small tool where they could change the pool sizes, the odds, the price, or switch any of the safety mechanisms on and off, and watch the cost curve move as they did it. They could see for themselves, for example, that the buy-back currency was doing more of the work than the pity mechanic alone, and decide from there whether that trade-off felt right for their game.

That's the job as I see it. Not to keep the math to myself, but to make sure the studio understood exactly what they were signing off on, and what it meant for the player on the other end.

This sits under the wider Monetization pillar, where the four core revenue models get broken down one by one. Here, it's just one system, and one lesson: design it with intent, then prove it before it ships.

If you want to see the mechanics

The story above is the short version. Here is a simplified version of the actual simulation loop, generic enough to adapt to your own banner, not tied to any specific game's real numbers.

import random

def simulate_one_player(sizes, odds, dupe_value, pity_ladder,
                         checkpoint_every, buyout_price):
    # sizes: unique items per pool, e.g. {"rare": 10, "uncommon": 50, "common": 25}
    # odds: cumulative pull odds for rare/uncommon/common, anything above is a "lucky pull"
    owned = {t: 0 for t in sizes}
    currency = 0
    pity_stage = 0
    pulls = 0

    def grant_new_or_dupe(tier):
        nonlocal currency
        remaining = sizes[tier] - owned[tier]
        if remaining > 0 and random.random() < remaining / sizes[tier]:
            owned[tier] += 1
        else:
            currency += dupe_value[tier]

    while any(owned[t] < sizes[t] for t in sizes):
        pulls += 1
        roll = random.random()
        tier_hit = next((t for t, cutoff in odds.items() if roll < cutoff), None)

        if tier_hit:
            grant_new_or_dupe(tier_hit)
        else:
            # lucky pull: pity protects the rarest pool first
            if owned["rare"] < sizes["rare"] and random.random() < pity_ladder[pity_stage]:
                owned["rare"] += 1
                pity_stage = 0
            else:
                pity_stage = min(pity_stage + 1, len(pity_ladder) - 1)
                unowned = [t for t in ("uncommon", "common") if owned[t] < sizes[t]]
                if unowned:
                    grant_new_or_dupe(random.choice(unowned))

        if pulls % checkpoint_every == 0 and owned["rare"] < sizes["rare"]:
            owned["rare"] += 1

        remaining_cost = sum((sizes[t] - owned[t]) * buyout_price[t] for t in sizes)
        if currency >= remaining_cost:
            currency -= remaining_cost
            break

    return pulls

Run that a few thousand times and read the spread:

def run_simulation(n_players, price_per_pull, free_pulls, **kwargs):
    costs = sorted(
        max(simulate_one_player(**kwargs) - free_pulls, 0) * price_per_pull
        for _ in range(n_players)
    )
    n = len(costs)
    return {
        "floor_p5": costs[int(n * 0.05)],
        "typical_p50": costs[int(n * 0.50)],
        "ceiling_p95": costs[int(n * 0.95)],
    }

Toggle the pity ladder, the checkpoint, or the buyout off one at a time and you'll see what I saw: the buy-back currency alone narrows the range more than the pity mechanic alone does, but stacking all three together is what gets every player close to the same, predictable price.

Designing an economy you'll have to stand behind?

Let's prove what it costs players before it ships, not after the reviews land.