Selected work / 04Contract
Frontier, 1899
A Minecraft roleplay server rebuilt around connected game systems.
- Role
- Lead engineer, contract
- When
- Oct 2026
- Stack
- Java 25, Paper, MariaDB, Node, Deepgram, Claude, ElevenLabs
- Status
- Live on the owner’s playtest server
Select a systemEach line is a real call from one system’s code into another’s.
Built onCharactersTowns and zonesContent in YAMLThreads and storage
01The problem
An 1899 frontier roleplay server: towns, outlaws and the law. The owner wanted it rebuilt from the ground up as one coherent plugin, with townsfolk players could actually talk to.
The goal was a world that reacts. A holdup should matter to the sheriff, the shopkeepers, the newspaper and the next person you talk to.
02Why it was hard
01 · One tick
Everything shares 50 ms
Game state lives on the server thread. Databases, voice and network can never block it.
02 · Money
It has to add up
Balances have to survive crashes, retries and duplicate events.
03 · A model in the loop
Fast, and never in charge
A spoken reply has to start in about a second, and the model can never break the game’s rules.
03What I built
- Threading modelGame state, the ledger and the Bukkit API on the server thread; player loading in pre-login; one database writer batching every second; a small read pool for history and reports; voice framing and socket I/O on their own threads. Guards check the boundaries and throw in development.
- LedgerAccounts in memory, changed synchronously on the server thread and written behind in batches. Every movement is one transaction row holding both sides, operations carry idempotency keys, balances can’t go negative, and a reconciler proves every balance equals its transactions. If writes fall behind for five minutes, money stops moving.
- LawWanted levels per town. Word reaches the sheriff’s office after a delay, the bell rings, and deputies ride in on horseback. They need unbroken line of sight before the first shot, miss more on the move, and stop at lawless ground. Jail counts online time and is saved so a crash can only shorten a sentence. Bounties pay any player who brings you in, with limits per pair of accounts.
- Voice NPCsA persistent websocket to a Node service: Deepgram for speech, Claude for the reply, ElevenLabs for the voice. What a character knows is built live from game state, and anything they do comes back as a request the server checks on its own thread.
- Towns and trainsTownsfolk who run for cover from gunfire and can be pickpocketed, a newspaper, and push-pull trains that board passengers between towns and carry a robbable express.
- The gangThe Redrock Boys: camps, holdups on the stage road, bounties on named men, and grudges.
- ShippingCI builds, tests against a real MariaDB, boots the server as a smoke test and scans for secrets. A deploy builds a commit, backs up the world and database, health-checks the new build and rolls itself back. Bots rehearse each feature on an offline copy.
04Decisions
| Decision | Instead of | Because |
|---|---|---|
| Accounts in memory, written behind in batches | A database transaction per operation | No database call ever runs on the tick, and both sides of every transfer persist in one row. |
| The model asks; the server decides | Letting the model change game state | Money never moves on a model’s word. |
| Deputies exist only during a chase | Persistent NPCs | Nothing piles up and nothing needs cleaning after a crash. |
| Keep game-state changes on one thread | Sharing mutable game state across threads | This reduces shared-state races. Tests run against a real MariaDB, including crash tests, and gameplay is checked by playing it. |
05Results
From the end of a sentence to the first word back
Speech to text384 ms
Model, first word0.6–0.9 s
Voice189 ms
Method: per-stage medians from the voice service’s benchmark scripts on one server, prompt cached.
- ~45 s
- down per deploy, stop to healthy
- ~2.5 min
- for a failed release and its automatic rollback
- 5 min
- database write-failure limit before ledger activity stops
Method: deploy timings measured on the rehearsal server. The five minutes is the ledger’s own limit: once writes have failed that long, money stops moving.