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 model
    Game 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.
  • Ledger
    Accounts 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.
  • Law
    Wanted 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 NPCs
    A 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 trains
    Townsfolk 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 gang
    The Redrock Boys: camps, holdups on the stage road, bounties on named men, and grudges.
  • Shipping
    CI 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

DecisionInstead ofBecause
Accounts in memory, written behind in batchesA database transaction per operationNo database call ever runs on the tick, and both sides of every transfer persist in one row.
The model asks; the server decidesLetting the model change game stateMoney never moves on a model’s word.
Deputies exist only during a chasePersistent NPCsNothing piles up and nothing needs cleaning after a crash.
Keep game-state changes on one threadSharing mutable game state across threadsThis reduces shared-state races. Tests run against a real MariaDB, including crash tests, and gameplay is checked by playing it.

05Results

~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.