Repository navigation
Server core design
The design for the server core we're building: how one process splits the game between owners, how every message is declared, how state reaches the database, how the core is hosted, and how we check all of it against the client. It's agreed and mostly not built yet; Roadmap says in what order it gets built and how far along that is. Creature AI, the volume of combat rules and the volume of content are out of scope here: this page fixes their shape, not their contents.
One process holds three kinds of owner (the account, the realm, and one per map instance), each single-threaded over its own state and reached only through its message queue. A gateway holds every client connection and routes each message by an opcode table, checked against the tag the client already puts on most of its messages. Every message is declared once, with the width of every field, and its reader and writer are generated from that declaration and checked against the client's own layouts. An open-world zone is one map that everyone in it shares. The core is built to be hosted publicly, and ground truth is the client and play, never agreement with another server.
NexusTomorrow started from NexusForever's game_rework branch. We kept the leaves that carry over cleanly (the game table layer, the enums, cryptography, file IO, the map tools) and we're rewriting the trunk: process topology, threading, entity lifecycle, persistence, the codec and verification. The login front door, permissions and GM command definitions lived in the trunk, so they come back later, lifted from the old code when the core is ready for them.
The trunk gets rewritten either way. The player core, maps, the send path and persistence are mostly plumbing that this design replaces, whichever route we took.
The riskiest systems need mechanisms the old trunk doesn't have: one event dispatch, transfers between owners, replication that serializes a change once, server-side geometry, a settled content API. Retrofitting them into code written on the assumption they don't exist costs more than building on them from the start.
Most of the future is new behaviour anyway. The old server sent 270 of its 609 message models. The game rules port: about 40 to 45% of the old game code is rules that carry over nearly as written.
The cost is a dark period. There's no playable server of our own until milestone one runs. What would reverse the decision is a need for a continuously playable server built from our own code before then, or too few hands to carry the porting. Neither holds today.
The client is fixed, and these facts about it shape everything below.
| Fact | What it decides |
|---|---|
| Most client messages travel under a routing tag, one fixed per opcode: 3, 11 or 19, which we read as the world, the realm and the account. Some go untagged, the login and character select's requests among them, and some tagged ones go before the world | Three owning sides; the opcode table routes to them and checks the client's tag |
| One socket per session, and the client changes server only at login or Realm Select | Maps in other processes would need every session relayed to them; in one process the gateway hands each message straight to its owner |
| The client owns its own movement and drops server movement for a unit it controls | Corrections revoke control, move with a time reset, and grant it back |
| Replication is absolute snapshots, with no culling or cap on the client, and duplicate creates are ignored | AOI (area of interest, who is sent what) is ours, and resending current state is always safe |
| The client never extrapolates another entity: after its last movement key, it holds there until the next one arrives | Sending a distant entity fewer keys means playing it later, not just sending less |
The update interval is 125 ms by default, settable from 25 to 1,000 ms per instance in WorldSettings (0x00F1), with a clock per entity. The client sends its own movement at that interval |
The tick rate and the update interval are ours to choose, per instance |
| The server decides every spell outcome; the client predicts only the global cooldown | Every hit test and all combat geometry live on the server |
| The client evaluates the same table rows the server must: spell conditions, target rules, prerequisites, telegraph shapes | Shared evaluators have to agree with the client's |
| The transport pings with raw opcode 0 every 500 ms and expects opcode 1 back | The gateway answers it |
| 384 game tables, all loaded by the client, with their schema embedded | All 384 are typed data, and the client's schema is the authority |
| Never shipped with the client: spawns, patrols, loot, vendor stock, creature AI, encounter scripts, trigger volumes | Ours to invent or source |
| A cast is sent once its blocker has 500 ms or less left | The server keeps one pending cast per caster, not a tolerance |
Crowd control needs two messages with one id: the CCStateSet spell effect and message 0x087F
|
Crowd control is a two-message protocol |
Effect ticks never run on the client, and effect flag 0x2 means "until removed" |
The server emits every tick's result |
flowchart TD
CL["Client connections"] --> GW["Gateway"]
WEB["Console, portal, CLI"] --> CMD["Command layer"]
GW -->|tag 19| AC["Account owner"]
GW -->|tag 11| RE["Realm owner"]
GW -->|tag 3| MA["Map owners"]
GW -->|telemetry, reports| OPS["Operations sink"]
CMD --> AC & RE & MA
AC & RE & MA -->|saves, loads| WB["Write-behind queue"]
WB --> DB[("PostgreSQL 18")]
| Owner | Holds | Runs on |
|---|---|---|
| Account | Accounts, entitlements, account currency, account-wide collections | Its own thread |
| Realm | Characters at rest, presence, chat, social, guilds, groups, housing communities, mail, matching | Its own thread, built as modules |
| Map, one per map instance | The world: its entities, and the characters in it | A shared pool for the long tail of maps, or a dedicated thread for a heavy one; each map runs on one thread at a time, to its own deadline |
The gateway frames, decrypts and unwraps each message, looks up its opcode's row in the opcode table, checks the client's tag against it and posts the message to the owner's message queue. It also runs the login exchange itself, and never touches game state. A message the client sends untagged is routed by its row alone, so login goes to the account owner and character select to the realm. Everything else in the process, the web host and the write-behind queue included, talks to owners by message.
| Knob | Default | Sets |
|---|---|---|
TickMs |
25; 125 for housing plots and small social instances |
The map tick per instance type, 40 Hz in the open world. It divides 250 ms |
ReplicationEveryTicks |
5; 2 for housing plots and small social instances |
The update interval per instance type, 125 or 250 ms, sent to the client in WorldSettings (0x00F1) |
MapPoolThreads |
not set yet | The shared pool that runs the long tail of maps |
DedicatedMapThreads |
not set yet | How many threads heavy maps may hold between them |
CastQueueWindowMs |
500 |
How early a queued cast may arrive, the client's own default |
EventCascadeLimit |
not set yet | How deep events raised by event handlers may chain in one drain |
MaxUnsentBytesPerSession |
not set yet | The soft limit on a session's unsent backlog |
UnsentOverLimitSeconds |
about 10
|
How long a backlog may stay over the soft limit |
HardUnsentBytesPerSession |
several times the soft limit | The backlog that drops a session at once |
ConsoleUrl |
loopback | Where the admin console listens |
Which map types start on a dedicated thread, and how many overruns promote a pool map, are data as well. AOI and sleeping cells have their own settings, listed with them below. The gateway's, the owners' and persistence's knobs are on their own design pages.
The core is built as these systems. Each one's design is on this page until the work to build it is planned; then it moves to a page of its own, and an explanation and reference pages follow once it lands, as Documentation standards describes. This table always points at a system's current page.
| System | Does | Described on |
|---|---|---|
| Host | Composition root, configuration, start-up and orderly shutdown | Host design |
| Gateway | Client connections: framing, decryption, the opcode table, login, and each session's writer | Gateway design |
| Codec | Reads and writes every message from one declaration | Codec design, Message audit design |
| Owners | The account, realm and map owners and their message queues | Owners design |
| Map scheduler | Runs maps to their deadlines on the pool and dedicated threads | Ownership and the tick |
| Timer wheel | Millisecond timers, waits and cooldowns per owner | Ownership and the tick |
| Event stream | Typed game events per owner, dispatched from a queue | Events and read models |
| Read model | What a map needs from the realm or account, pushed as refreshes | Events and read models |
| Entities | World entities, units, players and creatures, and player components | Entities, replication and the codec |
| Replication | Dirty sets, AOI, serializing once, the flush | Entities, replication and the codec |
| Persistence | Change sets, the write-behind queue, id blocks, one context per schema | Persistence design |
| Rules | The cast evaluator, the effect engine and the shared formula curves | Authority and combat |
| Content | Tables, content files, the importer and validator, trigger volumes | Content and encounters |
| Encounters | C# scripts bound by data id, the signal router, waits | Content and encounters |
| Command layer | Typed admin commands, permission checks and the audit | Command layer design |
| Web host | The admin console and the player portal | Admin console and player portal |
| CLI | The local command front end | Command layer design, Host design |
| Harness | The server in-process with headless clients, and its controls | Harness design |
| Tools | Bring-up, the content importer and validator, later the geometry bake | Repository and releases |
The systems step 3 builds first, the host, the gateway, the owners, persistence, the command layer and the harness, have design pages of their own, linked from the systems. What's below is the rest of the core, and the decisions that span systems.
One world. An open-world zone is one map instance that every player in it shares, never copied into layers, shards or channels, so the people in a place can always see and interact with each other. A crowded zone is answered by making its map's tick cheaper: AOI, update rates, a dedicated thread. Instanced content (dungeons, raids, PvP, housing) stays instanced, as the game designed it. That makes one map's capacity the ceiling on how many players can gather in one place, and the load test under Hosting measures exactly that.
The owners themselves, their message queues, how a character passes between them and the protocols between them are on Owners design. What follows is how maps share the process and keep time.
Maps share a pool, and heavy maps get their own thread. A pool sized to the cores runs the long tail (housing plots, small instances, quiet zones), earliest deadline first, each tick run to completion. Map types named in data as heavy (capital cities, raids, warplots) start on a dedicated thread, and a pool map that keeps overrunning its tick is promoted to one and returns to the pool when it settles. Pool and dedicated threads together stay within the cores left after the gateway, the realm and account owners and the garbage collector.
The map tick runs 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. A missed tick is skipped, not replayed, and every tick records its duration and allocations.
The tick and the update interval are set per instance type, as data in the world files, with a runtime-settings override per type that takes effect at the next map load.
- The tick divides 250 ms (25, 50, 125 or 250 ms), so every effect tick on a multiple of 250 ms, 98% of them, still lands exactly.
- The update interval is a whole number of ticks inside the client's 25 to 1,000 ms, sent to the client, and fixed for the instance's lifetime.
- The open world, capitals, dungeons, raids and PvP start at a 25 ms tick and 125 ms updates; whether PvP needs a shorter interval is the load test's call. Housing plots and small social instances start at a 125 ms tick and 250 ms updates. All of them get tuned from play at milestone one.
Every rule is in milliseconds, never in ticks. Timers, cooldowns and effect ticks live on a timer wheel, so the tick rate only sets the quantization. Cooldowns are absolute timestamps.
Only what a player can reach runs. Each grid cell in a map carries an awake flag, recomputed when a player crosses a cell boundary.
| State | When | What runs |
|---|---|---|
| Awake | A player is within the wake radius | Full AI, sent to clients |
| Offline | An ambient population with no player near | A zone director moves the population's record at its own cadence; no creatures exist |
| Asleep | Static and named spawns with no player near | Nothing. Respawns and cooldowns stay correct because timers are absolute |
The radii nest (fold back beyond become real, become real at or beyond wake, wake beyond AOI's admit radius), so nothing is ever seen asleep and records don't thrash, and a cap limits how many records turn real per check. Creatures in combat, encounters in progress and map-level systems (public events, respawn timers, the director) stay awake regardless. The settings are the wake radius, the become-real and fold-back radii, records turned real per check, and the director's cadence per map type. The core builds the cell flags, the skipping, a slot in the tick for map-level systems on their own cadence, and the call that turns a record into creatures and back; what the director decides belongs to the combat track.
Nothing ticks or saves in step. Each map's first deadline and its save sweep are jittered.
Note
On Windows the core waits on a high-resolution timer, because .NET's timers there step in 10 to 16 ms increments, too coarse for a 25 ms tick. On Linux it sleeps.
Entities are classes in a shallow hierarchy that mirrors the client's entity types: world entities, units, players, creatures and the rest. A player's state is a set of components, each registered once against a lifecycle: load from the saved character, tick only if it opts in, snapshot for saving, send on world entry at a declared position, dispose. The world-entry order is client protocol, so a start-up check fails if a component skips the lifecycle or is missing from the order.
A change is serialized once. Each entity keeps a dirty set; at the flush, each changed message is written once into a pooled buffer and fanned out to every session whose AOI list holds that entity. Each session's single writer then encrypts its own copy with its own key.
A slow client catches up instead of replaying. A newer update of the same kind for the same entity replaces an unsent older one in a session's send queue (conflation), so what lingers in a lagging backlog is events and entry bursts. Two limits decide when a session is dropped, and no other session ever waits on it:
-
Past the soft limit,
MaxUnsentBytesPerSession, a session is dropped only if its backlog stays above it forUnsentOverLimitSeconds, so world entry, a packed capital or a decorated house never trips it. -
Past the hard limit,
HardUnsentBytesPerSession, it's dropped at once, which bounds memory against a client that reads slowly on purpose.
The console shows each session's backlog.
AOI lives inside each map, and everyone in range is visible. An entity is admitted at one radius and dropped at a larger one, with a limit on swaps per check. Distance sets how often an entity updates, not whether it's seen: nearby entities at the instance's update interval, distant ones in slower tiers.
-
A slower tier sends fewer keys and plays the entity later, by a timestamp offset per tier. The client never extrapolates, so fewer keys on the old schedule would make a distant player stop and start. An entity that changes tier gets a
TimeReset, so its stream never switches clocks silently. - Creatures go as path or spline segments the client plays on its own, which costs little at any distance.
- A per-viewer cap stays only as a safety valve, set high per map type and filled by priority: group first, then target, then whoever is attacking the viewer, then nearest.
The settings are the admit and drop radii, the tiers and their update rates, the safety cap per map type, the priority order and swaps per check, with defaults set from play at milestone one. The cost is that a crowded map's outbound traffic grows with what its players can see, which the load test measures.
Movement is validated and relayed. The server checks speed, jump distance, clock skew and any displacement the client runs itself, and keeps each entity's timestamps monotonic. On a violation it revokes control (0x0639), moves the unit with a time reset, and grants control back (0x0636).
Each message is declared once. A declaration is a C# type marked with its opcode and direction, every field carrying its bit width and signedness, plus four computed shapes: a string, a counted array, a union picked by a type field, and a byte-aligned blob. A source generator writes the reader, writer and size for both directions, so the test harness's client side comes from the same declaration as the server's. A field without an explicit width is a build error. A one-off converter turned the old message models into declarations, and was deleted once its work was proven. Codec design has the declaration format, the generator's rules, the facts table and the conversion.
Each owner has one typed event stream, about 46 events. Five of them carry nearly half the bindings: creature killed, progress granted, entity activated, area presence and quest completed.
Dispatch is queued. Events raised during a tick are drained after the entity update and before the replication flush, looping until the queue is empty. Events raised by handlers join the same drain, up to EventCascadeLimit levels deep; past that the cascade is logged and cut. No handler ever runs inside another handler's stack.
Consumers find their rows through indexes, not scans. Quests, achievements, challenges, path missions, public events, reward tracks and scripts match table rows through indexes built at load, keyed by type and data id, so an event touches only the rows that name it.
Crossing owners is a message. Five kinds of event leave a map: guild progress, realm-first claims, content results, account grants and read-model refreshes.
Prerequisites read a pushed read model. Of 273 prerequisite types, 170 can be answered from the map's own state and 38 need data the realm or account pushes as refreshes, such as guild membership and entitlements. The other 65 are unknown or deprecated, and fail closed.
Every collection check goes through one interface: mounts, pets, flair, costumes, dyes and titles. Each type declares whether it's per character or per account, as data, and storage follows that scope.
The server decides everything except the player's own movement, which it validates, and displacement the client runs itself (ForcedMove, effect type 3, and VectorSlide, type 142), which it validates against the effect's own math. It runs the same math for creatures.
The cast evaluator does the client's checks, from the same columns: edge-to-edge range (minimum, maximum and vertical), facing, line of sight and its two opt-outs, the 11-bit condition vocabulary, crowd-control masks, the global cooldown kinds and charges. A failed check returns the cast result the client would show. A cast that arrives while its blocker has CastQueueWindowMs or less left takes the caster's single pending slot and starts when the blocker ends.
Effects run on the timer wheel in milliseconds: delay, tick, duration, and "until removed". The server emits every tick's result. Handlers for the 150 effect types decode their data words through the client's own per-type schema, and get built in order of how often each type appears in the tables.
Telegraphs are nine shapes. A target is hit when its centre lies inside the shape grown by its hit radius (not grown under flag 0x200), gated by the effect's phase flags.
Crowd control is two messages with one id, the CCStateSet spell effect and message 0x087F, removed with 0x0880, and timed by the same GameFormula curve the client displays. The rating curves are one shared family, for the same reason.
Two interim defaults hold until the evidence exists. Line of sight passes, counted and logged, until the geometry bake exists. The server revokes a player's movement control for the length of a stun or root, until an in-game check shows whether the client already blocks its own movement.
The map tick allocates nothing in steady state, asserted in the test harness. The runtime uses Server GC, switched to sustained low latency after load.
All 384 tables load once at start-up as typed, immutable data, each checked against the client's embedded schema.
World data is files, and the database is built from them. One JSONL file per zone, one record per line: spawns, trigger volumes, spline bindings, our vendor stock and loot, creature overrides, script bindings. Records are sorted by id with a fixed key order and canonical numbers, and hold no client strings. An importer rebuilds the read-only content schema from the files in one transaction.
A private pack can layer over the public files. Should a licence ever bring server data that can't be public, such as drops, spawns or creature AI, the importer layers a pack from the server's own disk over the public Content/ files, the private one winning. Public CI validates the public pack and the local tier validates the private one. Nothing from a private pack reaches the repository: no ids, constants or names in code, tests or commits. Behaviour in it is data the public engine runs, because on the FSF's reading a private script assembly loaded into the server would form one combined program with it under the AGPL.
Ids are permanent. A spawn id carries its zone: the world id in the high bits and a per-zone sequence below.
The existing world database is converted once, by a direct parser: 78,031 spawns plus hand-built instances.
Validation runs in two tiers. The public tier checks format, id uniqueness, enum ranges and that every script name exists. The local tier, which needs the client's tables, checks every id against the client and reports coverage: worlds without spawns, vendors and quest givers not placed. That report drives content work.
Creatures' presentation comes from the client's own data. A small per-creature sequencer runs the client's Creature2Action rules (birth, death, combat and awareness events, random branches, waits) as visual effects, plus the bark text. It carries no fight behaviour, because those rules carry none.
Encounters are C# scripts bound by data id: a spell's script id, a creature, a map or instance, a quest, a public event or a trigger volume.
- A signal router dispatches spell effect type 81 (selector, signal id, payload) by signal id.
- Handlers cover spawn, death, entering combat, signals and entering a volume.
-
await Wait(ms)resumes on the map's timer wheel, on the map's thread, without allocating, and is cancelled when its entity despawns or its map unloads. - Scripts hold ids and look them up again after every wait. The registry is built at start-up.
Trigger volumes are content too, keyed by the volume ids the tables already reference. A one-time generator seeds them from the objectives' marker locations, which cover 468 of the 511 volumes; the other 43 are placed by hand. Each map tests only players against them, through its grid, with hysteresis on enter and leave.
Collision, line of sight and navigation come from an offline bake of the client's terrain and model files, extending the map generator. Its output is built from the client, so it stays on your machine with the rest of your client data.
Every admin action is a typed command, whichever front end it comes from; the commands, their permissions and their audit are on Command layer design. This section is the web side.
The web host is Blazor Server inside the process, on its own listener, bound to loopback. Exposing it is a TLS reverse proxy and a configuration change, never a code change.
Signing in to the web uses the same credential as the game. Holding any console permission requires an enrolled TOTP second factor, enforced at the admin gate and when a role is granted. Sessions live on the server with expiry and revocation, sign-in attempts are rate-limited, antiforgery is on, and the host refuses to start without a configured Data Protection key.
The console accepts only its own origin. WebSocketOptions.AllowedOrigins is set, since ASP.NET Core accepts WebSocket connections from any origin by default; CORS is off on the Blazor hub, with a Content Security Policy, X-Frame-Options and WebSocket compression off.
Runtime settings are a typed registry (type, limits, default, description, when a change takes effect). Owners read an immutable snapshot at the start of each tick, swapped by the command layer, and the audit records old and new values.
The player portal replaces creating accounts by console command. It covers registration with email verification, password reset (which writes a new SRP6 verifier), account settings, two-factor enrolment, character pages, and a link to the running server's source, which the AGPL's section 13 owes every player. The message of the day carries the same link.
The core is built to be hosted publicly, and a fresh install is safe by default. Every listener binds to loopback until it's configured otherwise, which still suits hosting for yourself beside your client. A public deployment is configuration, never a code change.
Linux is the reference host, Ubuntu LTS, the same as the CI runner. Windows stays supported for development and for hosting beside the client; the map tick's wait is the only code that differs by OS. Before Linux is called the reference, the table and map extraction runs once on Linux, since the client's paths come from a case-insensitive file system.
A public deployment adds what one process can't do for itself:
- PostgreSQL backups with point-in-time recovery, and a restore that's been tested;
- metrics exported from
System.Diagnostics.Metricsto an outside collector through OpenTelemetry, as well as to the console; - a host with network-level DDoS protection, since nothing inside the process stops a flood.
A load test finds the ceiling. Once the core skeleton lands, a harness scenario puts hundreds of headless clients in one map. Its numbers say how many players one place holds, whether PvP needs a shorter update interval, and when anything below has to change.
It stays one process until one of three things holds: one machine is no longer enough, which the load test says; script faults the process can't survive (a stack overflow, running out of memory, a loop that never yields) take the server down; or garbage-collection pauses shared by every map exceed the tick budget. The split then is map-host processes, each running a pool of maps behind a gateway, with the realm and account in a core process. Layering isn't one of the options.
Ground truth is the client and play. Nothing is verified by agreeing with another server.
Every message is checked against a client-facts table: per message, its direction, whether it's fixed or computed, its total bits on the empty path, and its field widths in order. The table is our own data, public in the repository. It catches wrong widths, missing or extra fields, wrong enum widths, opcode swaps between different layouts and order swaps of different widths. It can't see swaps of equal width, signedness or meaning. Generated round-trip tests cover every message.
The test harness runs the server in-process with headless clients speaking the real protocol; its controls and the gates that fail a test are on Harness design.
In-game checks are a short checklist per feature on the real client. A test-only add-on, installed only in your client's own add-on folder, records every stock add-on's status and errors after world entry.
| Tier | Runs |
|---|---|
| Public CI, GitHub-hosted, no client data | The codec facts check and round trips, rule tests on hand-made fixture tables, the migration guard, harness scenarios on fixture tables |
| Local, with your client's tables and maps | Harness scenarios on real data, local content validation, in-game checks |
Public CI never prints the client's data. No assertion or log prints a table row, and workflows run with read-only permissions, actions pinned by commit, and no self-hosted runners.
The layout is Source/, Content/ (the public per-zone files), Tests/ and Tools/ (bring-up, the content importer and validator), with the client-facts table beside the codec. What's there today is on Repository layout.
Bring-up is one PowerShell script: check prerequisites, extract tables and maps from your own client, start PostgreSQL (installed, or one container), run the migrations, import content, start the server. It uses the same binary and configuration shape as a real deployment.
A pushed tag builds a release: a framework-dependent publish, one migration bundle per schema, and checksums. Versions are CalVer, and each release records the content version it imported. A server deploys only tagged releases, which keeps the source it runs public, as the AGPL's section 13 asks.
| Idea | What killed it |
|---|---|
| Refactoring the old trunk in place | The riskiest systems need mechanisms it doesn't have, and retrofitting them into code that assumes they don't exist costs more than building on them |
| Layering an open-world zone into copies, shards or channels | Players in one place have to be able to see and interact with each other. A crowded zone gets a cheaper tick instead |
| Maps in separate processes from the start | The client holds one socket per session and changes server only at login or Realm Select, so separate map processes need every session relayed to them. One process relays nothing, and the split waits for one of the three triggers under Hosting |
| A thread per map | Every house is an instance, so thousands of mostly idle threads would each wake for their own tick, and once they outnumber the cores they're preempted mid-tick |
| An entity component system | The client fixes the entity vocabulary and takes every change as its own message, so an ECS's flexibility buys nothing the client can use |
| A per-viewer cap that hides players in range | In one world everyone in range is seen. Distance changes how often an entity updates, not whether it's there |
| Checking our behaviour against another server | Agreement proves agreement, not correctness. The client and play are the reference |
| Default field widths in the codec | The old models' default 64-bit width for enums caused most of the 70 layout mismatches found against the client |
| Rules counted in ticks | Changing the tick rate would change every rule. In milliseconds, the rate is only a quantization knob |
| A tolerance for early casts | The client sends a queued cast inside a 500 ms window on purpose; a pending slot matches what it does |
| Reloading scripts while the server runs, in the first version | A restart is cheap until it's the bottleneck |
| Claim | Basis |
|---|---|
| The client facts in the table above | Established from the 16042 client and its tables, each checked on its own |
| What each routing tag stands for | Read from which messages travel under it; the message audit has verified the tag values, not yet their meaning |
| The telegraph hit rule | Inferred, not yet seen in game: the first thing checked once combat runs |
| The AOI tiers | Designed from how the client plays other entities, not yet seen in game: checked at milestone one's "see another player move" |
| Prerequisites: 170, 38 and 65 of 273 | Counted from the tables |
| 98% of effect ticks on a multiple of 250 ms | Counted from the tables |
| The old server's figures (270 of 609 models sent, 40 to 45% of game code portable, 70 layout mismatches) | Counted from the old code |
| The tick rates, 40 Hz and 8 Hz for housing | Chosen. Confirmed by measurement once the loop runs |
| How many players one map holds | Not known. Measured by the load test |
| Stun and root, and the client's own movement | Not known. The interim default holds until an in-game check |
| The world database's 78,031 spawns | Counted from its files |
- Tick rates: 40 Hz for most maps and 8 Hz for housing are chosen; measure once the loop runs.
- Milestone one: an in-game pass that opens every window once; what character select needs to offer every creation option, on Owners design; whether a distant player in a slower AOI tier moves smoothly on another client, and how a tier change looks.
-
The update interval: the client sends its own movement at the interval in
WorldSettings; whether it also plays out other entities' movement at that interval. - The load test: how many players one map holds, whether PvP needs a shorter update interval, and whether a lagging session's distant entities should move to slower tiers before it's dropped.
- Collection scope: per character or per account, settled for each collection type before its system is built.
-
The combat track: a general creature AI by archetype and difficulty, fed by per-creature ability lists that never shipped; the living world as the direction for creature behaviour, with a director per zone keeping off-screen life as records; lag compensation for telegraph hits, testing a target where its own client reported it at the moment of the hit and bounding how far back; whether stun and root block the player's own movement on the client; ForcedMove's details; the signal selector values and what a spell's script id means at run time; the meaning of the many
GameFormularows beyond those named here; collision and line of sight through the geometry bake.
Guides
Explanation
Reference
Design
- Server core design
- Roadmap
- Codec design
- Message audit design
- Host design
- Gateway design
- Owners design
- Persistence design
- Command layer design
- Harness design
About this wiki