Generators SMP
A DonutSMP-style economy Minecraft server — I designed the gameplay, wrote the custom server software, built the monetization model, and run the live infrastructure. It’s a small business and a systems-engineering project wearing the same hat.
The market gap
Most Minecraft servers monetize badly. Vanilla SMPs and minigame servers sell cosmetics and soft perks — nice-to-haves bolted onto gameplay that doesn’t really want you to spend. A small genre solved this: the economy SMP, with DonutSMP as the benchmark. There, money isn’t a side-shop — it’s woven into the core loop, and the result is a category that monetizes a Minecraft server like a free-to-play game.
GenSMP is built to occupy that proven model at early-DonutSMP scale (low hundreds of concurrent players). The thesis is simple: the same loop that makes the game compelling — grind passive income, climb tiers, defend your wealth, show it off — is also the loop that sells. The job is to design that loop honestly and let the monetization fall out of it.
The flywheel
The engine is the crate — a randomized, lootbox-style draw. A subscription’s recurring value is the keys it grants; the rank itself only adds cosmetics and minor convenience. A free loop feeds the same prestige engine — vote or refer a friend → earn keys → unbox → rare pulls broadcast to everyone — so free players power the population and the social proof. Critically, the store sells draws and vanity, never raw power: the game stays fair, which is what keeps the free majority playing. The cost side stays lean — a cheap Hetzner box bridges today’s scale, escalating to a dedicated OVH machine only once player numbers justify it.
Monetization, by design
Three subscription tiers, sold through a Tebex store that auto-grants the permission group on purchase. The deliberate choice: a subscription buys you a crate to open and some cosmetic flair — never an edge in the game itself. Extra home slots and a few seconds off a teleport warmup don’t move the needle on fairness; the lootbox draw and the chat tag are what people actually pay for.
* Free players still earn Voting-crate keys by voting. Each paid tier claims its crate on a recurring cooldown — that repeat draw is the subscription’s real value.
Crates — the product
The actual thing you’re buying: a randomized, lootbox-style draw. Four tiers (Voting → Gen+ → Gen++ → Ultra); subscribers claim theirs on a recurring cooldown, free players earn Voting keys by voting. Rare pulls broadcast to everyone with a sound — the draw is private, the win is public, and that’s what drives the next subscription.
Referrals
Invite milestones (5 / 10 / 25 / 50) pay out escalating crate keys, and the
top referrer wears a green [Friendly] tag. Organic growth, no ad spend.
Prestige tags
The #1 player on each metric gets a colored chat tag — [CEO]
(richest gens), [Mogged] (balance), [Slayer] (kills),
[DeGen] (playtime). Status you can only earn by playing — or paying
to play faster.
Sinks & market
A player auction house and tiered shops drain currency back out, so money
keeps its value. /sell is the floor price; selling through a
high-tier generator pays multiples more — another reason to climb.
Engagement, borrowed from mobile
The monetization only works if people keep coming back — so the core loop is paced with mechanics lifted straight from free-to-play mobile and idle games. A generator doesn’t earn forever: it fills a capped store over a few hours and then idles. Sitting full means leaving money on the table, so the optimal play is to return on a schedule and empty it.
That single rule — a ceiling plus a refill timer — is the same trick behind energy systems and crop timers in mobile games. It sets a natural session rhythm (log in roughly twice a day to keep everything productive), and it doubles as a balance lever: nobody banks runaway wealth by going AFK for a week.
Storage caps
↔ energy / resource caps. A full generator stops earning, so the cap itself is the nudge to come back — wasted production is the cost of not returning.
Fill timers (~6–8h)
↔ harvest timers (Hay Day, FarmVille). The refill window paces the day into a couple of natural check-in sessions.
Daily votes & crate cooldowns
↔ daily-login rewards & streaks. Voting refreshes every 24h for keys; subscriber crates recharge on a cooldown — both pay you to return on schedule.
None of this is incidental. Caps and timers turn a passive-income game into a scheduled habit, and daily-active players are the substrate everything else runs on: more sessions → more votes and social proof → more crate openings → more reasons to subscribe. The retention loop is what makes the monetization loop work.
Server architecture
Today it runs as a single tuned box — enough for launch. The scaling target is a proxy-fronted, multi-server topology built around Folia, the multithreaded Minecraft server. The whole architecture is shaped by one hard constraint, explained below.
Runs as a systemd service (auto-restart on crash/boot), console
driven over local-only RCON, firewalled to the game + vote ports. Realistic
ceiling: ~20–45 concurrent. A deliberate cheap bridge.
Why Folia — the one constraint
Standard Paper ticks each world on a single thread. Add 15 more CPU cores and the world still advances on one of them — so a busy SMP hits a wall around 100–200 players no matter the hardware. Folia breaks the world into regions that each tick on their own thread, so a 16-core box finally does 16 cores of work. That’s why the target hardware (OVH 9950X) and the software (Folia) are a single decision: the cores only pay off if the tick parallelizes.
Make the plugin Folia-ready
Phase 0Migrate ~30 scheduler call-sites from the legacy Bukkit scheduler to Folia’s region / entity / async schedulers. These APIs exist on Paper too, so the code runs today and is Folia-ready. The hard part: the one global generator-tick loop has to become per-region.
Bigger box + a proxy
Phase 1Move to the OVH 9950X, put a Velocity proxy out front for queueing and failover (the proxy secret is already stubbed in config). Still Paper, but more headroom — ~150 concurrent.
Flip to Folia
Phase 2Swap Paper for Folia (now a jar swap, not a rewrite) and replace the few Folia-incompatible plugins. Regionized ticking finally uses all 16 cores → several-hundred concurrent.
The leverage is in Phase 0: doing the scheduler migration now, while the plugin still runs on Paper, turns the eventual Folia switch into a jar swap instead of a rewrite. Front-loading the hard part is the whole strategy.
The backend
Every generator, sale, raid and listing is persisted. The data layer is small but was hardened by a real outage — and it’s where the most interesting performance work lives.
Early on, the database ran on a single connection with no write-ahead log. Under
about three players every subsystem funnelled through that one
connection and the server started throwing
Connection is not available, request timed out after 30000ms. The
fix is the current design: SQLite in WAL mode (concurrent reads
alongside a writer), a HikariCP pool of 4, a 5-second busy
timeout, and batched async writes so the main game thread never
blocks on disk. A MySQL backend is a config toggle for when it’s needed.
What’s stored
generatorsEvery placed generator — type, owner, location, tier, stack, stored, lifetime earnings, raid timers.player_statsPer-player lifetime earnings per generator type — feeds the leaderboards.auction_listingsPlayer market: serialized item blob, price, 48h expiry, atomic status.auction_collectionReturn bin for expired / cancelled / overflow auction items.raid_logImmutable audit of every successful raid (raider, target, loot).referralsOne-referrer-per-player mapping (enforced by primary key).key_claimsCrate-key cooldown tracking (3-day claim window per donor tier).pending_*_rewardsOffline queues — votes, keys, referral rewards delivered on next join.The core table
CREATE TABLE generators (
id TEXT PRIMARY KEY, -- generator instance UUID
type TEXT NOT NULL, -- dirt … netherite
owner TEXT NOT NULL,
world, x, y, z, -- block location
tier INTEGER, -- 1–10
stack INTEGER, -- 1–16
stored INTEGER, -- items waiting to be claimed
lifetime DOUBLE, -- $ earned since placement
protected_until BIGINT, -- raid grace window
post_raid_cd BIGINT
);
-- indexes by (world,x,y,z), owner, type → O(1) lookups
-- writes are batched every 30s via flushDirty() → one commit, not N The performance win — ticking only what matters
A passive-income server can accumulate tens of thousands of placed generators. The naïve loop ticks all of them every second — and it did, until it didn’t scale. Now generators are indexed by chunk, and each tick only touches the chunks that are actually loaded:
Walk every generator ever placed, every second — and clone a location object for each. A cliff at ~5k gens.
A world → chunk → generators index. Tick only loaded chunks. 10k+ generators run with no main-thread stall.
Same idea throughout: O(1) lookups by id and location, an auction purchase that
claims a row atomically (UPDATE … WHERE status = 0) so a crash can’t
double-spend, and a 30-second batch flush that collapses thousands of tiny writes
into one transaction. Capacity knobs (max-players, simulation-distance) are tuned
against spark profiles, not guesses.
The game underneath
All of the above exists to serve one loop:
Why I built it
I wanted to ship a real product — one with users, money, and consequences when it breaks — and own every layer of it. GenSMP is that: I designed the economy, wrote the ~dozen interlocking systems behind it, set the prices, and I’m the one who gets paged when the database locks up at peak.
The part I find most useful to have done is the monetization design — and the constraint I set on it. The store sells crate draws and cosmetics, never power, because the moment paying wins you the game you lose the free majority who make it worth playing. So the work was making a fairness-neutral model still convert: why the lootbox pull has to feel good, why the rare win has to be public, where currency sinks go so the economy doesn’t inflate. That’s incentive design as a system — the same reasoning a business problem needs, with a feedback loop fast enough to watch in real time.
And the engineering kept me honest. The database outage at three players, the generator loop that fell off a cliff at scale, the decision to do the hard Folia migration now rather than during a fire later — each one was a lesson in finding the real constraint and building around it instead of around the symptom.
It’s the clearest example I have of working end to end: strategy, system design, implementation, and operations — held together by one person who has to make them all agree.