Repository navigation
Roadmap
The order we build the server core in, and where it stands. What each step builds is on Server core design; this page is about sequence and progress.
We build the core in six steps, and each one ends green in public CI and, where it has one, passes its check in game. The first goal anyone can see is milestone one: log in, create a character, enter the world, walk, see another player move, and /say. Each step gets its own plan once the step before it is done, so every plan starts from code that exists rather than code we assume.
The order follows what each step needs to be tested at all.
- The codec comes before anything that sends a message, because nothing that talks to a client can be tested until messages can be declared. The codec's converter also needs the leaves split out of the old code first, so steps 1 and 2 share one plan.
- Messages are checked before the core skeleton uses them, because a message the client misreads fails silently, far from its cause. Milestone one's go first, and the skeleton starts as soon as pregame's are verified, so the project stops depending on the old server; the rest of the audit carries on alongside the build.
- The core skeleton comes before milestone one, because the milestone's checks run in the test harness, and the harness drives the server through the command layer the skeleton builds.
- The console follows milestone one straight away, since by then there's a server worth administering.
- Systems go in dependency order. Content first, because nothing happens in an empty world. Events and the read model before quests and achievements, because those consume them. Combat last, because it needs the geometry bake.
| Step | Delivers | Done when | Status |
|---|---|---|---|
| 1. Repository | The seeded repository, cut to the leaves that carry over, renamed, with nullable reference types on, tests and CI | Builds green with the leaves alone | Done on main
|
| 2. Codec | The declaration format, the source generator, a converter from the old message models, the client-facts table and its check | Every message declared; the facts check passes after fixing what it reports | Done on main: stage 3 closed after its review at c6b56e9. All 1,119 of the client's messages declared, 1,087 agree with the client's full layouts, 43 disagreements pinned. The message audit carries on alongside step 3: pregame has 41 of its 42 messages verified to their fields, world entry six and movement three |
| 3. Core skeleton | Owners and their message queues, the map scheduler, the timer wheel, the gateway and transport, the write-behind queue, the command layer and CLI, the test harness | Harness scenarios for routing, the owner protocols, saves and the allocation gate pass | Stage 1, the pregame slice, is done on main at e8da4aa: the real client logs in through our own server and creates a character, on PostgreSQL 18, with no part of the old server running. Its systems are on Host, Gateway, Owners, Persistence, Command layer and Harness, and Running a server has how to run it. Stage 2, the map side, is next |
| 4. Milestone one | Pregame on the lifted login front door; character list, create and select; world entry; movement relay; seeing others; /say
|
The milestone scenarios pass, and an in-game pass shows no add-on errors | Planned |
| 5. Console | The web admin console and the player portal | Every command is refused without its permission and audited; the origin and second-factor rules hold | Planned |
| 6. Systems | The content pipeline and the world database conversion first; then events and the read model, quests and achievements, economy and social; the combat track with the geometry bake later | Each system's tests and in-game check | Planned |
The conversation that leads up to it, from logging in to the end of the loading screen, is on Client protocol. World entry takes these server messages and no more, sent in this order, with one report from the client before the last:
| Opcode | What it does | Note |
|---|---|---|
0x00AD EnterWorld
|
Moves the client into a world, with a point for the world to load around. The character's own position comes later, from the world | First. The client accepts it only once a character is selected |
0x0262 UnitCreate
|
Creates the player's own unit | Verified to its purpose; its fields aren't yet |
0x019B PlayerUnit
|
Names that unit as the player, the unit the client moves | Refused if the unit doesn't exist yet or has no base faction; a faction refusal can't be retried |
0x00F1 WorldSettings
|
The world's settings, among them the period of the client's movement timer | Sent on every world entry |
0x0636 MovementControl
|
Gives the client control of the unit's movement | The only way to grant it; without it the player can't move |
0x06BC PathState
|
The player's path | The one message whose absence shows an add-on error |
0x025E PlayerState
|
The player's own values: the amount of each currency, experience, item proficiencies, entitlements and the like. Most of the client's interface waits for it | Never a stub: even the first one has to be well formed, though its lists can start empty. Verified to its purpose; its fields aren't yet |
0x00F2 WorldReady, from the client |
Says the world and the player are loaded | Sent once PlayerUnit and WorldSettings have arrived |
0x0061 WorldEntryComplete
|
Ends the loading screen | The answer to WorldReady, and the only way to end it |
Seeing others adds their UnitCreate (0x0262), their movement relayed in UnitMovement (0x0638), and a destroy when they leave. /say adds 0x01C2 to the sender, which is how it sees its own line, and 0x01C8 to listeners, on the client's built-in Say channel (type 4, id 0).
The core sends a message once its purpose is verified and every member it sets is too, the rest going as defaults, and handles a client message only once its fields are verified (Message audit design; decided 2026-10-18, built with the map side). So what's left of the audit's path to milestone one is the members world entry sets in UnitCreate (0x0262), PlayerState (0x025E) and UnitMovement (0x0638), the fields of the client's own ClientMovement (0x0637), and chat. The tagged wrapper the client sends many of its messages in, TaggedMessage (0x038C), and the control messages around movement are verified.
Done on main:
- the repository seeded once from NexusForever's
game_reworkwith its full history, and the old server trunk deleted in one commit; - the eleven projects that carried over renamed to
NexusTomorrow.*; - the SDK pinned, central package management, and a test workflow that runs only when dispatched;
- the SRP6 session key fixed to always be 64 bytes (it came out at 63 bytes on roughly one login in 256, which would give the client and server different world ciphers);
- nullable reference types on in nine of the eleven projects, with
Network.AuthandNetwork.Worldleft for step 2, which has since turned them on; - the code standard adopted and enforced in the build, with the tree normalized, formatted and renamed to it.
Both run-time checks the nullable work left owed are done, on a machine with a client: MapGenerator extracted all 384 tables for step 3's first stage, and ClientConnector wrote its config.json from the first-run prompts and started the client. ClientConnector has to be run from the client's Client64 folder for now: from anywhere else it writes its settings where it doesn't read them.
The codec is built in three stages, and the first two are on main, each reviewed. Stage 1 is the machinery: the bit reader and writer, the hot-path switch, the declaration language, the source generator, and the client facts table with its check. Stage 2 declared every one of the old message models, 1,254 of them, and proved 1,227 declared types bit for bit against the old code; the 26 it couldn't prove are defects in the old code. Stage 3 has settled the opcode list and the swapped fields, brought the empty-path check down to 55 pinned disagreements, and deleted the old code: its reader and writer, its packet layer, the converter, and the copy of the old models the conversion was proven against. The facts check is the proof that remains. The design, and what's still open in it, is on Codec design.
Stage 3 is closed, and an audit of every message carries on alongside the build. We assume every message and opcode we inherited is wrong until the client says otherwise. Every message the client registers is checked against the client: its full layout first, then what it's for and what each of its fields means, and for the flows milestone one needs, the order the client expects. Each declaration records how far it has been verified, and a test holds every message to that, so nothing is used before it's verified. Until a message is verified it carries a neutral name and its fields carry no meaning, so the code never claims something nobody has checked. The results are published on this wiki as a protocol reference generated from the declarations, and generated again as each area lands. Milestone one's messages are checked first, and step 3 is built on them as they're verified. The whole design is on Message audit design. The neutral names, the verification check and the full-layout check are in, and all 1,119 of the client's messages are declared. The first area, pregame (login, realm select and character select), has been checked as far as milestone one needs: 41 of its 42 messages are verified down to their fields, the login request (0x0592 LoginRequest) last, and those 41 are the first messages the core may use, character creation (0x025B CharacterCreate), the character list (0x0117 CharacterList) and world entry (0x00AD EnterWorld) among them. World entry came next, and six of its messages are verified to their fields, from naming the player's unit (0x019B PlayerUnit) to ending the loading screen (0x0061 WorldEntryComplete). Two of milestone one's world-entry messages are verified to their purpose but not yet their fields: the unit create (0x0262 UnitCreate) and the player's own values (0x025E PlayerState). The tagged wrapper, TaggedMessage (0x038C), is verified to its fields. Movement came next: taking control (0x0635 MovementControlReceipt), ending it (0x0639 MovementControlEnd) and the zone change (0x063A ZoneChange) are verified to their fields, and the movement messages themselves (0x0637 ClientMovement, 0x0638 UnitMovement) to their purpose. What's left for milestone one is the members the server sets in what it sends, the client's movement down to its fields, and chat.
There's no upstream. The repository was seeded once and is never synced: no cherry-picks, and no comparing against NexusForever's server. The trunk is replaced, so upstream's changes to it don't apply, and the leaves have already moved on.
One plan per step, written when the step before it is done.
One world. An open-world zone is one map that every player in it shares, never split into layers or shards, so the people in a place can always see each other. Capacity work goes into making a crowded map cheaper to run, and once step 3 lands a load test measures how many players one map holds. Server core design has how.
Built to be hosted publicly. The core is designed, built and documented for a public deployment: Linux as the reference host, client ports hardened against hostile input, backups with a tested restore, outside monitoring, and DDoS protection at the host. A fresh install still binds everything to loopback, so going public is configuration. Server core design has the details.
Out of scope for the core:
| Area | Why |
|---|---|
| Creature AI and the volume of combat rules | The combat track's work. The core fixes the combat contract and the effect engine's shape |
| Content volume | The core builds the pipeline; filling the world is content work |
| Idea | What killed it |
|---|---|
| Keeping a playable server running through the rebuild | It would mean refactoring the old trunk in place, which the server core design rejects. The cost is a dark period until milestone one |
| Syncing with NexusForever | The trunk it changes is the part we replace, and the leaves have diverged |
| Building game systems before the core skeleton | Every system would be written against plumbing that's about to change |
| Claim | Basis |
|---|---|
| Steps 1 and 2, and step 3's first stage, build and their tests pass | The CI run in the header badge: 724 of 724, step 1's seven among them |
| The SRP6 key fix | A test that fails on the old derivation and passes on the new one |
MapGenerator still works |
Run against a real client for step 3's first stage: all 384 tables extracted |
ClientConnector still works |
Run against a real client: it asked for the host and language, wrote config.json and started the client. Only from the client's Client64 folder, as above |
| The pregame server works with the real client | The client logged in and created a character on our server; a mixed-case email logs in too; a wrong password is still to be tried with it |
| Milestone one's message list | Established from the client; the in-game pass that opens every window once is still to do |
- Whether character select needs the account messages the old server sent with the list (
0x0968and its neighbours) for the client to offer every creation option, the Expert start and the level-50 entry among them. - Which systems in step 6 come after events and the read model, beyond the order above.
| Piece | Path |
|---|---|
| What each step delivers |
Source/, Tools/
|
| The tests behind each step's status | Tests/ |
| The CI workflow that runs them | .github/workflows/test.yml |
Running and working on it
The server core
- Server core design
- Roadmap
- Host · design
- Gateway · design
- Owners · design
- Persistence · design
- Command layer · design
- Harness · design
The protocol
This wiki