Technical Reference

MoralDecay

GMCP Specification

Version 1.0  ·  Last Updated 2026-07-31  ·  playdecay.com

MoralDecay implements the Generic MUD Communication Protocol (GMCP, telnet option 201) to push structured, out-of-band game data to clients — vitals, room information, combat targets, chat, and GUI/map auto-configuration for Mudlet. This document is intended for players and developers building GMCP-enabled clients or scripts.

Note on capability negotiation Unlike some IRE-style MUDs, MoralDecay does not implement Core.Supports.Set. The server sends its fixed set of packages to any connection that successfully negotiates GMCP, regardless of what the client claims to support. There is no per-package opt-in/opt-out on the wire, aside from the GUI auto-push toggle noted in §3.
Contents
  1. 1. Connecting with GMCP support
  2. 2. Negotiation sequence
  3. 3. Packages (server → client)
  4. 4. Messages the client can send
  5. 5. Known limitations
  6. 6. Quick reference table

1Connecting with GMCP Support

Web client has no GMCP support The web client's traffic does not reach the driver directly. It flows browser → WebSocket → mud-websockify (public port 2023) → mud-telnet-proxy (internal, localhost-only port 3004) → driver (port 3003). The internal proxy is a small purpose-built script that negotiates just enough telnet (NAWS, TTYPE, ECHO) for the xterm.js terminal to work correctly in a browser. It does not recognize the GMCP telnet option (201) and actively refuses (WONT/DONT) any option it doesn't explicitly know, and it discards the payload of any IAC SB … IAC SE subnegotiation block for unrecognized options — exactly the framing GMCP uses. As a result, GMCP packets never reach the browser client, by design.

Note that port 3004 is bound to 127.0.0.1 only and is not reachable from outside the server — it isn't an alternate port you can connect a client to; it's purely an internal hop behind the public websocket endpoint (port 2023). If you're building a GMCP-aware client, connect directly to port 3003 instead.

2Negotiation Sequence

  1. Telnet-level GMCP option negotiation (IAC WILL/DO 201) is handled by the driver itself.
  2. Once negotiated, the mudlib immediately sends:
    Core.Hello {"name": "MoralDecay", "version": "1.0"}
  3. At this point the player's character file has usually not finished loading — negotiation happens mid-telnet-setup, before login completes. If so, only Core.Hello is sent; nothing else follows immediately.
  4. The full snapshot (Room.Info, Char.Vitals, Char.Stats, Char.Status, Char.Limbs, Client.Map, Client.GUI) is sent once login completes and the player actually enters the game.
  5. On reconnect after link-death, where the player file is already loaded, the full snapshot is sent immediately instead.
Client guidance Don't assume a snapshot follows Core.Hello. Initialize your UI from the actual Room.Info/Char.Vitals packets whenever they arrive, not from the negotiation handshake itself.

3Packages (Server → Client)

All packages below are push-only; the server does not respond to client-initiated requests for room/character data (see §4 for the one exception).

Core.Hello S → C

{"name": "MoralDecay", "version": "1.0"}

Sent once, at GMCP negotiation.

Room.Info S → C

{
  "num": 123456789,
  "name": "A dusty crossroads",
  "area": "Candera",
  "terrain": "indoors",
  "exits": {"n": 234567890, "e": 345678901},
  "coord": {"x": 3, "y": -2, "z": 0},
  "room_type": "shop"
}

Sent on login and every room move.

Char.Vitals S → C

{"hp": 87, "maxhp": 100, "mp": 42, "maxmp": 60}

Sent on login, heartbeat healing ticks, mana change, and every hit taken.

Char.Stats S → C

{"str": "strong", "dex": "agile", "con": "hardy",
 "int": "clever", "wis": "wise", "cha": "charming"}

All six values are adjective strings, not numbers, matching the wording in score. Sent only at login/reconnect — stat changes from leveling, death penalties, or gear are not currently re-pushed, so this can go stale mid-session. Reconnect to refresh.

Char.Status S → C

{
  "name": "Excelsior", "level": 42, "class": "warrior",
  "race": "human", "xp": 15000, "align": "Good",
  "silver": 3200, "wiz_level": 0, "wizard": false
}

Sent at login and on XP gain/loss (including party XP splits).

Char.Limbs S → C

{"head": "is in excellent condition",
 "left arm": "is wounded",
 "right leg": "is LOST"}

One key per body part, keyed by the limb's name (varies by race — no fixed schema). Values are free-text condition phrases matching diagnose/health output. Sent at login, on heartbeat, and on every hit taken.

Target.Info S → C

{
  "name": "a snarling wolf", "race": "wolf",
  "condition": "is badly hurt and bleeding",
  "lost_limbs": ["left ear"],
  "equipment": "It is wearing a studded collar."
}

Sent on examine, when your main attacker changes, and when attackers are added/removed during combat. Cleared with {"name": ""} when the target is lost or combat ends.

Comm.Channel S → C

{"chan": "tell", "player": "excelsior",
 "msg": "hey there", "direction": "in",
 "targets": ["someoneelse"]}

Sent for shout, tell, party, wiz, and aristo channel traffic.

Client.Map S → C

{"url": "https://www.playdecay.com/downloads/gui/newbie_map.dat"}

Points Mudlet at a newbie-area map file for auto-load. Sent once at login.

Client.GUI S → C

{"version": "1.0.7", "url": "https://www.playdecay.com/downloads/gui/MoralDecay-GUI.mpackage"}

Points Mudlet at the official GUI package for auto-install/update; Mudlet re-fetches only when version changes. Sent once at login.

Opt-out Players can disable this auto-push entirely via an account setting — absence of Client.GUI does not necessarily mean anything is broken.

External.Discord.Info / .Status S → C

External.Discord.Info {"inviteurl": "", "applicationid": "1511789438747803698",
 "largeimagehash": "", "largeimagename": "moraldecay",
 "smallimagehash": "", "smallimagename": "", "buttons": []}

External.Discord.Status {"game": "MoralDecay", "user": "excelsior",
 "details": "excelsior", "partysize": 0, "partymax": 0,
 "starttime": 1785600000}

Sent in response to the client sending External.Discord.Hello (§4).

4Messages the Client Can Send

Only one inbound GMCP message is recognized:

External.Discord.Hello

Triggers the server to reply with External.Discord.Info followed by External.Discord.Status (§3). Any other client-sent GMCP package or message — including Core.Supports.Set — is silently ignored.

5Known Limitations

6Quick Reference Table

PackageDirectionTrigger
Core.HelloS → CGMCP negotiation
Room.InfoS → CLogin, room move
Char.VitalsS → CLogin, heartbeat, hits, mana change
Char.StatsS → CLogin/reconnect only
Char.StatusS → CLogin, XP change
Char.LimbsS → CLogin, heartbeat, hits
Target.InfoS → CExamine, attacker change, combat end
Comm.ChannelS → CChannel traffic
Client.MapS → CLogin (if enabled)
Client.GUIS → CLogin (if enabled)
External.Discord.Info/StatusS → CIn reply to External.Discord.Hello
External.Discord.HelloC → SSent by client to request Discord data