Owners design - NexusTomorrow/server GitHub Wiki

design, published verified 2026-10-18

The design for what's still to build in the owners: the map owners, the hand-off of a character between the realm and a map, the protocols between owners, and the realm's modules. Step 3's first stage built the owner base, the account owner and the realm owner, described on Owners and its references; this page holds the rest. How maps are scheduled is on Map scheduler design, their timers on Timer wheel design, and how far the build has got on Roadmap.

The idea

In short

Every map instance is an owner too, run to its own deadline by the map scheduler, and it holds the characters in it. A character passes between the realm and its map by a hand-off that never leaves it in neither or both. Owners reach each other only through request-and-reply protocols, and the realm grows as modules on its one thread.

Why

The client's three routing tags read as the account, the realm and the world, and the world is the maps' side. Most of the client's traffic belongs there, so a map has to own what's in it outright, and anything that crosses to the realm or the account has to cross as a message, never as a shared reference.

Decisions

Locked

Each message queue drains by priority. High-priority messages (movement, casts, targeting) drain in full every tick, held in check by their rate limits so they can't starve the rest. Normal messages drain within a time budget per tick with a guaranteed floor, and the leftovers wait a tick. The budget runs from the start of the tick, and a stopping map drains everything.

Queued work holds ids, never references. Queued messages, timers and continuations look their entity up again when they run, and drop out if it's gone.

The watchdog watches maps too, and reports a stall with its stack. It reads its list of owners again on every check, so maps that open and close are watched as well.

A failure costs one session or one entity, not the server. In a map, a message handler that throws disconnects its session and hands the session's character back to the realm. A map update that throws skips that entity for the tick and counts the fault; repeated faults stop the map, disconnecting its players with their state as of the last confirmed save. No catch hides an error.

A character is at rest in the realm or held by exactly one map, never both and never neither. On world entry the realm hands the character off to the map, which owns it until it leaves or logs out and then hands it back (P6, below). Every arrival ends in a character the map holds or one handed back, and every select ends in an arrival or a refusal, so no path leaves a character unselectable until a restart. Maps feed the realm a location record for /who, friends and local chat.

Request-and-reply protocols carry every message whose first check and owner differ. The client tags a message by where its first check runs, not by who owns the result, so forwarding is normal and the boundary doesn't move. Nine are designed, and the set grows as the message audit finds flows; if a busy flow ever crosses owners, the owner split is revisited rather than the protocols stretched.

Protocol Direction Used by
P1 Map to realm, after a local check Groups, matching
P2 Realm to map Local chat, item links, guild notices
P3 Escrow, map to realm Sending mail, listing on the market
P4 Release, realm to map Taking mail, market fills, the guild bank
P5 Account spend The storefront, account currency
P6 Character handoff, realm to map and back World entry and exit
P7 Map to map, through the realm Housing donations and placement
P8 Account to realm Account-wide friends and presence, for the realm's social module
P9 Account grant to a map Claims and character purchases from the storefront, into the inventory of the map holding the character

Every account change a map asks for, and every account grant a map receives, goes reserve, confirm, apply, commit: the Try-Confirm/Cancel (TCC) pattern.

The realm is built as modules: presence, chat, social, guilds, groups, housing communities and the realm core, each owning its data and calling the others directly on the realm's thread. Moving one into an owner of its own later is a table edit and a thread, taken when the load test shows the realm is the bottleneck, presence and chat first. A map keeps the same kind of seams (positions, combat, inventory and economy, progression, housing) on its one thread.

The realm owner

A character's look travels as item visuals. The character list and entity creates show equipped item visuals, not the look choices made at creation; the server turns those choices into item visuals through the client's character customization table. Until then a new character shows without its chosen face and hair.

Selecting a character hands it to its world's map. The realm loads the full character through the write-behind queue, under the character's own key, and hands it to the map that hosts its world. A select ends in an arrival or a refusal with the general reason: for a character the session doesn't list, a load that finds nothing, a world no map hosts, or a map that won't take it. A load that lands after its session closed is dropped and the character freed. A character away from rest is never loaded twice and can't be deleted.

A second login waits for the hand back. When a client crashes or is killed, its character may still be in the world as the next session selects it. That select waits until the map hands the character back, then loads it, and reads the map's last save because both use the character's key. A newer waiting select replaces an older one, which is refused with the general reason.

The map owners

One shared map per open world, made on first entry. The client's own world table, from the operator's client data, says which worlds are open world. The new-player tutorial and the maps around it carry a marker no continent has, and they're held, along with every instance type, until that marker is understood: a select into one is refused with the general reason, and the server warns at start-up about any start location that names one.

A map ticks in a fixed order: drain the message queue, run due timers, update active entities, drain the event queue, flush replication on the update interval, and hand dirty state to the write-behind queue. The second stage builds the drain, the timers and the saves; entity updates, events and the flush come with other players and creatures. Every tick records its duration and allocations, and a steady-state tick allocates nothing.

What a map keeps of a character: its world, zone, position and facing. The zone comes from the client's own reports (0x00F2 WorldReady and 0x063A ZoneChange), and a report of zone 0 keeps the zone the character stood in: on taking control the client repeats the zone it last reported, which is 0 when it hasn't reported one yet. Position and facing come from the client's movement, applied once the client holds control.

Saves are staggered and only when something changed. A character's first save falls at a random point within one save interval of its arrival, and each next one an interval after the last, so a map full of arrivals never saves in step. A save is queued only while the character changed since its last one, and once more on every hand back.

Logging out follows the client's own steps. The client sends the same request however the player chose to leave. The map answers it with a countdown, zero for now, waits for the client's confirmation, hands the character back, and sends 0x0594 WorldLogout to return the client to character select on the same connection (Client protocol).

The hand back runs in one order: the map removes the character, cancels its timer, queues its save, posts the return to the realm, and only then lets the session go. The save is queued before the return, so a load the realm queues after the return, on the same key, reads it.

A stopping map hands everyone back. Its last tick drains what's queued, hands every character back, an arrival still queued included, and disconnects them. On shutdown every map stops while the scheduler still ticks, then the realm and account owners stop, and the server waits for every save.

Knobs

Knob Default Sets
Start locations none An operator points each faction at one of the four classic starting zones (Northern Wilds, Everstar Grove, Crimson Isle, Levian Bay), with the world and the point taken from their own client data
Maps:SaveInterval 60 s How often a map saves a character that changed; at least 1 s
Maps:NormalDrainFloor 32 Normal-priority messages a map handles each tick before its budget applies
Maps:NormalDrainBudget 5 ms How far into a tick a map keeps draining normal-priority messages once past the floor

Open

  • Start locations come from content once the content pipeline exists.

Rejected

None recorded for what's left. The owners' rejected ideas are on Owners.

Verification status

Claim Basis
The three routing tag values, fixed per opcode Established from the client and verified by the message audit
What each tag stands for Read from which messages travel under it; not yet established by the audit
The zone report on taking control, 0 before any other Established from the client; the reference has it
That a character enters, leaves and re-enters where it stood with the real client Owed: an in-game check once the second stage lands

Open questions

  • Whether character select needs the account messages the old server sent with the list (0x0968 and its neighbours) for the client to offer every creation option, the Expert start and the level-50 entry among them. A real login shows it.