Repository navigation
Client Web Protocol
TL;DR: The web client and the server talk over one WebSocket with JSON frames
{"command": name, "args": [...]}in both directions. The server sends text (console_out), a state object after every command (prompt) and about thirty other events. The client sends typed lines (console_in) and about twenty settings and editor commands. Output text carries a few HTML-like tags that the client turns into links, menus and colours. Use when: you write a client for DreamLand, add a server event or an RPC command, change what a prompt field holds, or emit a web tag from C++ or Fenia. Source of truth:mudjs/src/websock.js(transport),mudjs/src/manip.js(tags),plug-ins/iomanager/descriptor.cpp(wsHandlePayload,RPCRUN),plug-ins/iomanager/interprethandler.cpp(build_prompt),plug-ins/web/impl.cppandplug-ins/comm/who.cpp(prompt listeners),plug-ins/output/mudtags.cpp(tag output),plug-ins/descriptor/websocketrpc.cpp(WebList, list frames) Verified: 2026-11-04 against dreamland_code 64cc9099, mudjs 990bf6d (event and command lists generated from both trees);run_left,config_stateandconfig_changedrows 2026-11-05 against dreamland_code f0587220
- URL:
wss://dreamland.rocks/dreamlandfor the game. A client served from the page with#buildin the address uses the build server instead (see Client-Web-Development). The engine listens on a plain WebSocket port (1234,dreamland_world/ports/1234.xml) and expects the subprotocolbinary. The TLS and the/dreamlandpath come from a reverse proxy in front of it (unverified, not in the repos). - Frames are UTF-8 JSON text, one object per message:
{"command": "console_in", "args": ["look\n"]}. The client setsbinaryType = 'arraybuffer'and decodes withTextDecoder. - The server drops a whole frame, without closing the socket, if any argument is an array or an object. Arguments must be scalars (string, number, boolean). Structured data travels as one JSON string, or as base64 (
macros_put,triggers_put,script_put). - One message may carry at most 512 KiB of payload. A larger one closes the socket.
- The client names itself by version. The server's first frames are
version(["DreamLand Web Client/2.1", nonce]) andmsp([true]). If the version string differs from the one built into the client, the client prints a message, drops its resume token and closes. Bump both strings together when the protocol breaks. - The first frame the client sends on a fresh socket is the answer to the codepage menu:
console_inwith1, which selects KOI8-R, the encoding the client decodes (telnet.jsmaps KOI8 letters back to telnet byte names). A client of your own can pick7for UTF-8 instead. The language menu that follows is a normal game prompt.
Some commands need no character and work at the login prompt: console_in, resume, account_enter, chat_subscribe, list_subscribe, ping, check_name, client_src. Every other command is looked up in the RPC registry and needs a logged-in character. Without one the server logs "no character for non-console-in RPC" and ignores the frame.
A phone tab is suspended after a few seconds in the background and its socket dies. To make that invisible:
- The server puts a short-lived token into every
prompt(resumefield) and intoresume_ok. - The client keeps it in
sessionStorage(it dies with the tab). - On a new socket the client sends
resume <token>instead of logging in. The server answersresume_ok [nextToken]orresume_failed [reason]. -
reasonisfinal(token dead: quit, expired or the body left the world, go to login) orretry(the old socket is not linkdead yet, try again after a pause, up to five times). - Any frame from the playing socket keeps the resume window open.
The client also sends ping every 25 seconds while in the world and after returning from the background. The server answers pong. Any incoming frame counts as proof of life. Fifteen seconds of silence closes the socket and starts a resume. Lines typed while the socket is in doubt are held (at most 20, for at most 15 seconds) and replayed after the resume lands.
Generated from the handlers in mudjs/src and checked against the C++ senders. list is the exception: mudjs has no handler for it yet. Names ending in _data/_saved are built from the request name in C++ (<name>_get answers <name>_data, <name>_put answers <name>_saved).
| Event | Arguments | Meaning |
|---|---|---|
console_out |
text | Game text with ANSI colour codes or the tags below. |
prompt |
object | State after each command. See Prompt fields. |
version |
version, nonce | Protocol version and the per-socket nonce for [cmd=] tags. |
msp |
on | Sound lines (MSP) may be played. |
notify |
text | Pop-up notice. |
alert |
text | Modal alert(). |
editor_open |
text, arg | Open the text editor (answer to webedit). |
cs_edit |
subject, body | Open the Fenia script editor (answer to cs edit). |
resume_ok |
next token | Resume accepted. |
resume_failed |
reason |
final or retry. |
pong |
none | Answer to ping. The client has no handler, any frame is enough. |
nanny_step |
step | Login dialogue reached a real question (sent by game scripts). |
account_enter_ok / account_enter_failed
|
none | Answer to account_enter. |
check_name_result |
{name, ok, reason} | Answer to check_name. |
account_chars |
{current, account, chars} | Characters of the account. |
chat |
object | One chat message: id (channel command name, stable across languages), kind (room, personal, area, world, race, group, mob, quest), dir (in or out), text (as printed, with colour tags), at (unix time), peer ({name, anon, npc, key}), and area, quest, step where they apply. Sent only after chat_subscribe. |
chat_state |
{on, playing, who} | Answer to chat_subscribe. |
list |
object | Structured copy of a list a command prints. See List frames. Sent only after list_subscribe. |
config_schema |
object | Settings schema and current values. |
config_state |
{playing, who, screenreader} | Sent on entering the world (playing: 1, the character's name, screenreader: 1 or 0) and on leaving it (playing: 0 only). |
config_changed |
{key, value} | A setting changed (from the game or another tab). Sent only after the client asked for config_schema, except screenreader, which is always sent. |
config_result |
{key, ...} | Answer to config_set. |
run_left |
{path} | A run stopped. path is the rest of the speedwalk as run takes it (3n2eN), empty when the run finished. A run dropped without a stop message (input flush, a trap) sends nothing. Written straight to the socket, so it arrives before the room text of the same pulse. |
aliases_char / aliases_acct / aliases_acct_saved
|
object | Aliases of the character and of the account. |
macros_data / macros_saved
|
object | Macros. |
triggers_data / triggers_saved
|
object | Client triggers. |
script_data / script_saved
|
object | The player's client script. |
autobuff_list |
object | Autobuff lines (with a seq that matches the request). |
widget_help_result |
{what, arg, text} | Text for a help sheet, see widget_help. |
boss_phase |
kind, tier | A boss changed phase (control or panic flashes the screen). Sent by game scripts. |
| Command | Arguments | Notes |
|---|---|---|
console_in |
line with trailing newline | One typed line. Character-less. |
resume |
token | Character-less. |
account_enter |
token | One-use entry token minted by the site after login. Character-less. Never travels as console_in, so it stays out of the command log. |
ping |
none | Character-less. |
check_name |
candidate name | Character-less. Availability only. |
client_src |
label | Character-less. The ?src= label of the link the visitor came by, kept for newcomer reports. Up to 32 characters of a-z 0-9 - _. |
chat_subscribe |
1 or off
|
Character-less. |
list_subscribe |
none or off
|
Character-less. Turns list frames on for this socket, or off. No answer frame. |
account_chars |
none | |
config_schema |
none | |
config_set |
key, value | |
aliases_get, aliases_acct_get
|
none | |
aliases_acct_put |
base64 | |
macros_get |
none | |
macros_put |
acct or char, base64 |
At most 32 KiB decoded per list, one put per second. |
triggers_get |
none | |
triggers_put |
acct or char, base64 |
|
script_get |
none | |
script_put |
base64 | |
autobuff |
none | |
autobuff_prefs |
list, tag or set, order, off, own, tag |
|
editor_save |
text | Result of editor_open. |
cs_eval |
subject, body | Post a Fenia script from the editor. |
widget_help |
what, arg |
what is quest, affects or whois (name of up to 32 letters). Runs the info command and returns the text as widget_help_result. It is not a second console_in. |
New server-side RPC commands are one RPCRUN(name) block (rpccommandmanager.h) that receives the character and a list of string arguments.
A client that sent list_subscribe gets a list frame from these commands: skills, glist (all groups, or one group), help with several matches, socials, commands (both views), vault, the note boards' list, areas and quest request. The text output does not change. The frame arrives before the text, because the text goes through the output buffer.
{"kind": "skills", "title": "", "rows": [{
"label": "bash, 75%", "id": "bash", "section": "6",
"data": {"level": 6, "available": true, "learned": 75, "real": 75, "moves": 13},
"actions": [{"verb": "practice", "cmd": "practice bash"}, {"verb": "help", "cmd": "help 676"}]
}]}-
kindnames the list:skills,glist,group,help,socials,commands,vault,notes,areas,quest_request. -
label,titleandsectionare in the reader's language, colour codes stripped.id,sectionanddataare optional. -
cmduses English command names, so it works in every language. - Verbs in use:
help,read,practice,open,do,social_at,get,filter,request. A client shows its own label for a verb it knows and the plain command otherwise.openandfilteranswer with another list.social_atstill needs a target name appended. - Reserved for future lists, not sent yet:
look,whois,tell,path,buy. -
vaultrows with asectionare type filters, rows without one are stored items. -
datakeys by kind:skillshaslevelandavailablealways, pluslearned,realandmanaormoveswhen available.grouphasusable.areashaslowandhighfor areas with levels.vaultitem rows havelevel.noteshasunread.
C++: WebList in plug-ins/descriptor/websocketrpc.h (row, action, data, send, active). It checks the subscription itself. Fenia has no way to send this frame yet: ch.writeWSCommand accepts only scalar arguments and does not check list_subscribe.
prompt arrives after every command that ends in a prompt. Fixed fields are always present. Block fields are sent only when their value changed since the last prompt. When a block empties, its value is the string none, sent once, and the client hides that panel. The client merges each frame into window.mudprompt.
Fixed fields (build_prompt):
| Field | Meaning |
|---|---|
hit, max_hit, mana, max_mana, move, max_move
|
Vitals. |
vnum, area
|
Room vnum and area file name, for the map. |
sex |
neutral, male, female or either. |
lang |
Display language: en, ru or ua. |
resume |
Resume token (web only). |
fight |
Health percent of the current opponent, 0 out of a fight. |
rage, blind, faerie, hide, fade, poison, mind
|
0 or 1. Screen effects. They are flavour only, the real state is in the text and the affects panel. |
Block fields (listeners in plug-ins/web/impl.cpp and plug-ins/comm/who.cpp):
| Field | Content |
|---|---|
room, zone
|
Names in the viewer's language. |
exits |
{e: visible letters, h: hidden letters, l: e/r/u letter set}. |
sect |
{i: icon, n: name}. |
time |
{h: hour, tod: time of day word, l: light word}. |
date |
{d: day, m: month name, y: year, s: season}. |
w |
{i: weather icon, m: weather text}. |
group |
{ln: leader name, leader: member, pc: [member], npc: [member]}. A member is {sees, hit, max_hit, health, hit_clr, level, tnl}. |
det, enh, pro, trv, mal, cln, cd
|
Affect columns (detect, enhance, protect, travel, malediction, clan, cooldown): lists of {n: label, d: ticks left}. d is -1 for permanent. |
affsn |
Flat list of the English names of active affects, used by the autobuff gate. |
p1 |
{ps: [6 permanent stats], cs: [6 current stats]} in the order of the stat table. |
p2 |
{h: hitroll, d: damroll, a: armor, s: saves, pos: position, posf: position flags}. |
q |
Personal quest {t: time left, i: info line}. |
aq |
Area quest {t, i}. |
sp |
1 when the character has any castable spell (the spells button). |
fcmd |
List of combat command names for the buttons, empty out of combat. |
who |
{t: total, p: [{n, s, r, cn, cc}]}: name, sex letter, race (two letters), clan letter and colour class. |
The same object is sent to telnet clients that negotiated GMCP. See Client-Mudlet-and-GMCP.
The text in console_out is plain text plus a few HTML-like tags. The server only emits them for web readers. Telnet readers get the plain label. The { markup that produces them is described in Text-Markup. mudjs/src/manip.js turns the tags into elements.
| Tag | Client result |
|---|---|
<c c='fgbg'>text</c> |
A span with that class. The class is fg + b (bright) or d (dark) + the colour letter, for example fgbg bright green, fgdr dark red. |
<hc>look plaque</hc> |
Click sends the text as a command. |
<hc cmd='path 3001'>label</hc> |
Click sends cmd instead of the label. Only shapes on the allowlist SAFE_HC_COMMANDS are honoured, others fall back to the label. |
<hh>quest</hh>, <hh id='333'>text</hh>
|
Click sends help <id or text>. |
<hl>https://...</hl> |
External link in a new tab. Text must start with http. |
<hg>name</hg> |
Click sends glist <name>. |
<hs>6e10nd</hs> |
Click sends run <path>. |
<m c="drop $,wear $" l="repair $" i="1773732900">short</m> |
Item menu. c entries sit above the divider, l entries (only valid in this room, for example at a repairer) below it. $ becomes the item id i. The entries are command names in the viewer's language. |
[map=midgaard.are] |
Button that opens the zone page on the maps site. |
[read=sign знак,see=sign] |
Clickable word that sends examine 'sign знак'. The see word must be one of the words before the comma. The tag keeps its historical name read. |
[look=door,see=door] |
Clickable word that sends look door, used for exits. |
[cmd=quest $1,see=13,nonce=r4U78eAa] |
Clickable text that sends the command with $1 replaced by the see text. The nonce must equal the one from version, otherwise the tag is left as plain text. Built by web_cmd(ch, cmd, fmt) in C++ and .webcmd(ch, cmd, label) in Fenia. |
The nonce exists because player-typed text can reach other players with tags intact. A forged [cmd=...] would have to guess the nonce of the reader's socket.
- Emit tags only inside
{Iw ... {Ixso telnet and screen-reader readers keep a plain-text twin. See Accessibility. - Anything the player must do stays reachable as typed text. A click is a shortcut.
- A new
cmdshape for<hc>needs an entry inSAFE_HC_COMMANDS, anchored and as narrow as the command that emits it. - Strip
{htags from any text a player typed before sending it to other players.
Start here
Get running
Contribute
- Contribute
- Contributing Workflow
- Code Style
- Accessibility
- Writing Player Text
- Game Design Principles
Build areas
- Building
- OLC
- OLC Rooms
- OLC Mobiles
- OLC Objects
- OLC Resets
- OLC Line Editor
- OLC Other Editors
- Area Format
- Area Quests
- Mob Forms and Parts
Fenia
- Fenia
- Fenia Language
- Fenia Triggers
- Fenia Gotchas
- Fenia API
- Fenia Scripts
- Fenia Behaviors
- Fenia Skills and Spells
- Fenia Cookbook
- Fenia Grammar
Engine
- Engine Architecture
- Engine Time and Pulses
- Output Format
- Mechanics Combat
- Mechanics World
- Data Configs
- Item Power
Localization
Clients
More