All work

Live, parts in testing

Legacy rescue on a live server

Rebuilding inherited systems on a server that never stops: phones and radios from scratch, every plugin’s data onto MySQL, equipment that can’t lose items, and a trunk migration that moves every player’s storage without losing a thing.

Role
Design and implementation, working solo
Stack
Lua (GLua), NutScript, MySQL, Python for tooling
Context
Diverge Networks, a long-running roleplay server
Status
Phones, data layer and equipment live; trunks and speed in testing

The approach

Most of a long-running server is code someone else wrote, code that was never finished, or code written for a server that has since changed. I enjoy that. Sometimes the job is finding the one clean fix without waking all the other monsters. Sometimes it’s turning a hedge maze into a modern apartment.

Either way, players have to keep playing while it happens, and nothing they own can go missing on the way.

Phones and radios, rebuilt from scratch

Live since 2025

The old phone and radio system was unreliable enough that the server asked for a full rewrite: calls that never ended, players stuck in calls that no longer existed, and police channels that could be listened in on.

Screenshot coming soon

The phone interface while dialling.

assets/projects/phone-radio/phone-dial.png (tight crop)

Dialling.

Video coming soon

One call from dialling to answering to hanging up, with a second client answering.

assets/projects/phone-radio/phone-call.mp4 (about 15 seconds)

A call, start to finish.

Screenshot coming soon

The radio voice bubble while someone speaks.

assets/projects/phone-radio/radio-bubble.png (tight crop)

Talking on the radio.

Moving every plugin’s data onto MySQL

Live since 2025

Plugins saved their data to files on the server. I moved doors first, then every plugin, onto MySQL, with an upload command that brought the old file data across.

The new data layer guarantees each plugin’s reads and writes happen in the order they were made:

Code excerpt coming soon

The per-plugin queue: enqueue, run the next operation, and advance on success or failure.

assets/excerpts/plugin-data-queue.lua

Adapted from production code, lightly edited for readability. Never enough to rebuild the system.

Equipment that can’t get lost

Live since September 2026

Equipping clothing moves an item out of the inventory grid into a hidden state. The inherited equip code had no safe way back when something went wrong halfway, so items vanished.

I rebuilt equipping and unequipping as one promise-based API used by every item type, with safeguards at each step, and added a check on character load that finds items stuck in that hidden state, puts them back and tells the player what happened.

Code excerpt coming soon

Equipping as one promise chain, and the load-time check that rescues items stuck out of the grid.

assets/excerpts/equip-recovery.lua

Adapted from production code, lightly edited for readability. Never enough to rebuild the system.

Every trunk into one, without losing an item

Built, in testing

Each vehicle used to have its own trunk. They’re being replaced by one shared trunk per character, migrated automatically the first time each character loads after the update.

Trunk migration flow When a character loads, the shared trunk and overflow inventory are loaded. Items from each old trunk move one at a time into the shared trunk, or into overflow if it is full. A failed move puts the item back. Only after every item has moved does the vehicle's database row point at the shared trunk, and then the player is notified. Character loads Load the shared trunk and the overflowinventory, creating them if missing For each old vehicle trunk, move itemsone at a time, in sequence Fits: move it intothe shared trunk Full: move it to overflow,collected at the car dealer A move fails: put the item back, and leavethe vehicle on its old trunk for now Every item moved: update the vehicle'sdatabase row, then notify the player The database only changes once the last item is safe.
The migration runs automatically the first time each character loads after the update.

Video coming soon

A test character with items spread across several vehicle trunks loads in. The shared trunk opens with everything consolidated, then the lost-and-found at the car dealer shows the overflow.

assets/projects/legacy-rescue/trunk-migration.mp4 (20 to 30 seconds)

Migration on first load.

Screenshot coming soon

The shared 10 by 10 trunk inventory, partly filled.

assets/projects/legacy-rescue/shared-trunk.png (tight crop to the panel)

One shared trunk.

Code excerpt coming soon

The item transfer with fallback and revert, and the migration that only updates the database once every item has moved.

assets/excerpts/trunk-migration.lua

Adapted from production code, lightly edited for readability. Never enough to rebuild the system.

The trade-off

Removing an item and adding it elsewhere isn’t one database transaction, so a server crash between the two steps could still lose that one item. The revert path and the migration log keep that window small and visible, and it’s an acceptable risk for a one-time migration.

Speed, from last-writer-wins to composable modifiers

Built, in testing

Every system on the server set player speed directly. Carrying a lot of cash, being drunk and being tased all overwrote each other, and nothing restored the right value afterwards.

Code excerpt coming soon

The generated modifier API, the recompute step, and the legacy setter that makes gradual migration possible.

assets/excerpts/speed-modifiers.lua

Adapted from production code, lightly edited for readability. Never enough to rebuild the system.

Video (optional) coming soon

A player slowed by carrying cash and by being drunk at the same time, then recovering from one without losing the other.

assets/projects/legacy-rescue/speed-stacking.mp4 (about 15 seconds)

Two effects, one speed.

Smaller rescues