Projects / Game Platform Engineering
Corellia: Phantasy Star Online Server Platform
A three-instance private server platform (production, canary and legacy) for Phantasy Star Online: Blue Burst, migrated from Tethealla to newserv with staged rollouts, account and character migration between server generations, and imports from another community server.
- C++
- Python
- Django
- Docker
- MySQL
- GitHub Actions
The problem
A group of friends and I play Phantasy Star Online: Blue Burst, Sega’s 2004 online RPG, whose official servers shut down long ago. We’d been running it on Tethealla, an older open-source server written in C, on a single VM with a MySQL database and a Django account portal I had built for it. It worked, but it was a dead end: unmaintained code, missing features, and crashes in parts of the game nobody could fix.
newserv, an actively developed open-source server in C++, was the obvious successor, and it’s what Corellia runs on today: production and canary are both built from my newserv fork (79 commits of my own on top of upstream). Getting there wasn’t a drop-in change:
- Different data model. Tethealla stores accounts and characters in MySQL rows and blobs; newserv uses its own file formats and an HTTP admin API.
- Real people, real progress. Players’ characters, banks and admin rights had to survive the move intact, and nobody should lose an evening’s play to a botched cutover.
- The portal had to keep working throughout: sign-up, password resets, character views and server status, for whichever server a player was on.
Architecture
| Instance | Role | Runs |
|---|---|---|
| Coronet (production) | Where players play | newserv in Docker, with its own portal |
| Canary | Every change lands here first | newserv :canary image and portal :canary image |
| Legacy | The original world, kept as an archive | Tethealla + MySQL on its own VM |
Each instance has its own account portal, branded per instance, served behind Cloudflare. Game and portal images are built by GitHub Actions and published to GHCR.
Key decisions
A backend interface, not a rewrite. I put the portal’s game access behind a GameBackend interface
with two implementations: TethellaBackend (the original SQL code, behaviour-preserving) and
NewservBackend (newserv’s HTTP API). Views pick the backend by configuration, so one codebase serves
the legacy and modern servers, and the migration could happen incrementally rather than as a big-bang
rewrite.
Migration as code, run from the portal. Data moves through tested Django management commands:
- Accounts: copied with admin flags mapped to newserv’s permission model. Migrated accounts start with a temporary password, and a portal gate forces a change on first login.
- Characters: converted from Tethealla’s database blobs into newserv’s binary character format by a parser and writer I wrote for that format, with banks and key configuration carried inside.
- Email: newserv has no email field, so the portal owns a small SQLite store for recovery addresses.
Later the portal gained an admin migration console, so standing up an instance and moving players no longer needs a shell at all.
A release line players can’t feel. The server fork and the portal share one discipline: feature
branch → canary → tested on the canary instance → main/master → production. Image tags are
guarded in CI, so only canary can publish :canary and only the main branch can publish :latest.
The character importer has its own test suite, and the image build is gated on it.
Fix the server, not just the config. In my newserv fork, client-side enhancements that players wanted (nine of them) are on by default for new accounts but stay overridable from the in-game menu, applied once per account. Other fork changes include per-kill rare-drop odds in the enemy info window, drop tables for the final bosses, and server logs that survive a container being recreated. Gameplay rates from the old server were ported by an idempotent script, so the new world felt the same as the old one on day one.
Beyond the server
- Imports in every direction. Players can copy characters between instances, either direction and pick-which, and import a character exported from another community server into a free slot.
- A modern client. Widescreen, an HD interface, controller support and a launcher, packaged into Windows installers and Steam Deck payloads by a self-hosted build runner, without any game assets ever entering Git. The portal serves the installers directly.
- Operations. Live tuning of server settings from the portal’s admin pages, monitoring of every instance’s server and portal, and a separate archive for the legacy world.
Results
- Moved from Tethealla to newserv, a maintained C++ server, with players’ accounts, characters, banks and admin rights carried over.
- Three instances with a canary in front of production, so changes are proven before players see them.
- A portal that outlived the migration: the same codebase serves both server generations.
- A long tail of fixes shipped through the canary, from crash fixes in specific game areas to quest-reward delivery and admin tooling.
What I’d do next
- Retire the legacy VM once the archive no longer needs a live server, keeping only a data export.
- Add end-to-end tests that drive a real client handshake against the canary on every release.