From 1a359a5ed76d1fbbc496400d34b949f64c443f62 Mon Sep 17 00:00:00 2001 From: Harry Cordewener Date: Fri, 31 Jul 2026 09:41:28 -0500 Subject: [PATCH] Write the reference section by hand, and measure every number on it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Spec §9's last block, and §14 counts it in v1. Clients, codebases, protocols and orientation content, hand-written and versioned in git — the sanctioned alternative to a wiki, which is on the Never list. The split that makes it worth having: the prose is ours and the figures are measured. "Games running this codebase: 47" comes from IGameQueries on the request that renders it, over the same filter the page's own listing link carries, so the headline and the listing behind it are one query and cannot drift. A codebase page whose count is a hand-typed number is the failure mode, and it is indistinguishable on the page from the honest version — so it is made impossible rather than discouraged, and a test asserts the count *moves when the catalogue moves* rather than merely that a number appears. Content is Markdown in content/reference/, embedded into the assembly and rendered per request. Razor would have saved a dependency and cost the thing the section exists for: governance here means review, review of prose only works if the diff reads as prose, and a codebase description inside a component arrives in a PR as escaped literals interleaved with layout. Structured claims live in a tiny key: value front-matter block read by our own parser — eight keys, no nesting, no YAML dependency, and an unknown key is an error because a mistyped `capabilty:` silently dropping a matrix row is the shape of wrong that looks right here. The Markdig pipeline is where "no external hosts" is enforced. Raw HTML is disabled and an image is rewritten into a link to the same URL, so no content file can make the rendered page fetch from anywhere — a property of the parser rather than of editorial care. Protocol pages carry only measured handshakes, and carry no complement. "12 of 40 offered CHARSET" leaves 28 games that did not offer it *to us*, a set mixing servers without it and servers whose handshake we have not read; ProtocolByCodebase has no third number for a renderer to reach for. Client matrices are hand-written, because a client has no handshake of ours to observe — so the citation is the load-bearing part of the record, and CapabilityClaim.Read demotes any state arriving without a source to unknown, in both directions. An unknown that reads as a no is the heatmap defect wearing different clothes. Screen-reader accessibility is the first row of every client page whether or not anybody established it: spec §9 names it, no incumbent publishes it, and an omitted row and an unknown row are the same fact with only one of them legible. Forty-four of the ninety cells across nine clients say unknown, and that is the honest number. Everything survives ?plain=1, rendered off the same records. Markdig's own ToPlainText was not usable: it collapses heading, paragraph, bullet and code block into one unwrapped line each, which is the page with its structure removed — so the plain renderer walks the AST instead. GameFilter grows a Codebase facet, matched as a bounded prefix so every patchlevel of PennMUSH gathers and ROMulus does not join ROM. It is applied after FieldPrecedence rather than in the WHERE clause, for the reason that class already refuses to write the ladder in SQL. /games and /api/games learn the same spelling, because the panel's field names are the public query language. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01NrGKmKcRCGktyhRTFbQDMk --- Directory.Packages.props | 13 + content/reference/client-atlantis.md | 46 +++ content/reference/client-beipmu.md | 46 +++ content/reference/client-blightmud.md | 47 +++ content/reference/client-mudlet.md | 55 ++++ content/reference/client-mushclient.md | 52 ++++ content/reference/client-potato.md | 46 +++ content/reference/client-tintin.md | 52 ++++ content/reference/client-tinyfugue.md | 45 +++ content/reference/client-vipmud.md | 43 +++ content/reference/codebase-aresmush.md | 33 ++ content/reference/codebase-circlemud.md | 27 ++ content/reference/codebase-cobramush.md | 25 ++ content/reference/codebase-coffeemud.md | 29 ++ content/reference/codebase-dikumud.md | 35 +++ content/reference/codebase-evennia.md | 33 ++ content/reference/codebase-fluffos.md | 32 ++ content/reference/codebase-moo.md | 29 ++ content/reference/codebase-muck.md | 28 ++ content/reference/codebase-pennmush.md | 48 +++ content/reference/codebase-rhostmush.md | 27 ++ content/reference/codebase-rom.md | 29 ++ content/reference/codebase-smaug.md | 27 ++ content/reference/codebase-tbamud.md | 24 ++ content/reference/codebase-tinymush.md | 38 +++ content/reference/codebase-tinymux.md | 36 +++ .../orientation-collaborative-roleplay.md | 63 ++++ content/reference/orientation-connecting.md | 46 +++ content/reference/orientation-families.md | 60 ++++ content/reference/protocol-atcp.md | 31 ++ content/reference/protocol-charset.md | 38 +++ content/reference/protocol-gmcp.md | 41 +++ content/reference/protocol-mccp.md | 40 +++ content/reference/protocol-msdp.md | 29 ++ content/reference/protocol-msp.md | 38 +++ content/reference/protocol-mssp.md | 59 ++++ content/reference/protocol-mxp.md | 39 +++ content/reference/protocol-pueblo.md | 32 ++ content/reference/protocol-tls.md | 37 +++ content/reference/protocol-ttype.md | 44 +++ src/MUI.Catalog/CodebaseFamily.cs | 90 ++++++ src/MUI.Catalog/Ecosystem.cs | 51 ---- src/MUI.Catalog/Facets.cs | 15 +- src/MUI.Catalog/Views.cs | 24 ++ src/MUI.Web/Api/ApiModels.cs | 6 +- src/MUI.Web/Api/GameFilterBinding.cs | 6 + .../Components/ClientCapabilityMatrix.razor | 84 ++++++ .../Components/Layout/MainLayout.razor | 1 + src/MUI.Web/Components/Pages/Games.razor | 12 + .../Components/Pages/ReferenceEntry.razor | 205 +++++++++++++ .../Components/Pages/ReferenceIndex.razor | 66 ++++ src/MUI.Web/Components/PlainText.cs | 2 +- src/MUI.Web/Components/ReferencePlainText.cs | 253 ++++++++++++++++ src/MUI.Web/MUI.Web.csproj | 20 ++ src/MUI.Web/Reference/ClientCapabilities.cs | 77 +++++ src/MUI.Web/Reference/ReferenceDocument.cs | 144 +++++++++ src/MUI.Web/Reference/ReferenceFigures.cs | 131 ++++++++ src/MUI.Web/Reference/ReferenceFrontMatter.cs | 140 +++++++++ src/MUI.Web/Reference/ReferenceLibrary.cs | 109 +++++++ src/MUI.Web/Reference/ReferenceMarkdown.cs | 230 ++++++++++++++ src/MUI.Web/wwwroot/app.css | 64 ++++ .../MUI.Catalog.Tests/CodebaseFamilyTests.cs | 58 ++++ .../ClientCapabilityMatrixTests.cs | 122 ++++++++ tests/MUI.Web.Tests/FacetSurfaceTests.cs | 1 + tests/MUI.Web.Tests/ReferenceContentTests.cs | 284 ++++++++++++++++++ tests/MUI.Web.Tests/ReferenceFiguresTests.cs | 169 +++++++++++ 66 files changed, 3851 insertions(+), 55 deletions(-) create mode 100644 content/reference/client-atlantis.md create mode 100644 content/reference/client-beipmu.md create mode 100644 content/reference/client-blightmud.md create mode 100644 content/reference/client-mudlet.md create mode 100644 content/reference/client-mushclient.md create mode 100644 content/reference/client-potato.md create mode 100644 content/reference/client-tintin.md create mode 100644 content/reference/client-tinyfugue.md create mode 100644 content/reference/client-vipmud.md create mode 100644 content/reference/codebase-aresmush.md create mode 100644 content/reference/codebase-circlemud.md create mode 100644 content/reference/codebase-cobramush.md create mode 100644 content/reference/codebase-coffeemud.md create mode 100644 content/reference/codebase-dikumud.md create mode 100644 content/reference/codebase-evennia.md create mode 100644 content/reference/codebase-fluffos.md create mode 100644 content/reference/codebase-moo.md create mode 100644 content/reference/codebase-muck.md create mode 100644 content/reference/codebase-pennmush.md create mode 100644 content/reference/codebase-rhostmush.md create mode 100644 content/reference/codebase-rom.md create mode 100644 content/reference/codebase-smaug.md create mode 100644 content/reference/codebase-tbamud.md create mode 100644 content/reference/codebase-tinymush.md create mode 100644 content/reference/codebase-tinymux.md create mode 100644 content/reference/orientation-collaborative-roleplay.md create mode 100644 content/reference/orientation-connecting.md create mode 100644 content/reference/orientation-families.md create mode 100644 content/reference/protocol-atcp.md create mode 100644 content/reference/protocol-charset.md create mode 100644 content/reference/protocol-gmcp.md create mode 100644 content/reference/protocol-mccp.md create mode 100644 content/reference/protocol-msdp.md create mode 100644 content/reference/protocol-msp.md create mode 100644 content/reference/protocol-mssp.md create mode 100644 content/reference/protocol-mxp.md create mode 100644 content/reference/protocol-pueblo.md create mode 100644 content/reference/protocol-tls.md create mode 100644 content/reference/protocol-ttype.md create mode 100644 src/MUI.Catalog/CodebaseFamily.cs create mode 100644 src/MUI.Web/Components/ClientCapabilityMatrix.razor create mode 100644 src/MUI.Web/Components/Pages/ReferenceEntry.razor create mode 100644 src/MUI.Web/Components/Pages/ReferenceIndex.razor create mode 100644 src/MUI.Web/Components/ReferencePlainText.cs create mode 100644 src/MUI.Web/Reference/ClientCapabilities.cs create mode 100644 src/MUI.Web/Reference/ReferenceDocument.cs create mode 100644 src/MUI.Web/Reference/ReferenceFigures.cs create mode 100644 src/MUI.Web/Reference/ReferenceFrontMatter.cs create mode 100644 src/MUI.Web/Reference/ReferenceLibrary.cs create mode 100644 src/MUI.Web/Reference/ReferenceMarkdown.cs create mode 100644 tests/MUI.Web.Tests/ClientCapabilityMatrixTests.cs create mode 100644 tests/MUI.Web.Tests/ReferenceContentTests.cs create mode 100644 tests/MUI.Web.Tests/ReferenceFiguresTests.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index 4c22078..dcdc0c8 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -42,6 +42,19 @@ + + + + + diff --git a/content/reference/client-atlantis.md b/content/reference/client-atlantis.md new file mode 100644 index 0000000..37f2570 --- /dev/null +++ b/content/reference/client-atlantis.md @@ -0,0 +1,46 @@ +--- +kind: client +slug: atlantis +title: Atlantis +summary: A macOS-only client, long-lived and long in beta. Its scripting is documented as no longer working, which is the one honest "no" in this section. +home: https://www.riverdark.net/atlantis/ +platform: macOS +capability: screen reader | unknown | +capability: TLS | yes | https://www.riverdark.net/atlantis/history.php +capability: UTF-8 | yes | https://www.riverdark.net/atlantis/history.php +capability: MCCP | yes | https://www.riverdark.net/atlantis/history.php +capability: GMCP | unknown | +capability: MSDP | unknown | +capability: ATCP | unknown | +capability: MXP | unknown | +capability: MSP | unknown | +capability: scripting | no | https://www.riverdark.net/atlantis/ +see-also: clients/mudlet +see-also: protocols/charset +--- + +Atlantis is a native macOS client that has been around since Mac OS X 10.3 and was updated for +64-bit in the Catalina era. It handles RFC 2066 character-set negotiation and Unicode, which is +better than its age would suggest, and it does MCCP and SSL. + +## The one "no" in this section + +Its scripting was Perl, through the CamelBones bridge, and the project's own home page says it no +longer works — Apple's handling of Perl changed and the library's author died some years ago. That +is a *sourced absence*, which is a different thing from an unknown, and it is the only cell in the +whole client section that carries one. Everywhere else the honest answer was that we could not +establish it. + +## Everything we could not establish + +The version history is complete and public and mentions **MCCP**, **SSL** and **charset +negotiation** — and never mentions GMCP, MSDP, ATCP or MSP. MXP appears once, as something intended +for a version after 1.0.0, which has not arrived. + +There is a Perl `Atlantis::Speak()` call in the scripting API, and it would be easy to read that as +screen-reader support. It is not: it is a scripted text-to-speech call in a scripting system the +project says does not work. VoiceOver, "accessible" and "screen reader" appear on none of the home +page, the downloads page, the full version history, or the archived user guide. + +The current download is 0.9.9.8, still nominally a beta, with no release date published anywhere on +the site. diff --git a/content/reference/client-beipmu.md b/content/reference/client-beipmu.md new file mode 100644 index 0000000..2ef683a --- /dev/null +++ b/content/reference/client-beipmu.md @@ -0,0 +1,46 @@ +--- +kind: client +slug: beipmu +title: BeipMU +summary: A Windows client aimed at the MUSH side of the hobby, with screen-reader support in the output window and Pueblo rather than MXP. +home: https://beipdev.github.io/BeipMU/ +platform: Windows +capability: screen reader | yes | https://github.com/BeipDev/BeipMU/blob/master/Assets/Changes.txt +capability: TLS | yes | https://beipdev.github.io/BeipMU/ +capability: UTF-8 | yes | https://beipdev.github.io/BeipMU/ +capability: MCCP | unknown | +capability: GMCP | yes | https://github.com/BeipDev/BeipMU/blob/master/Documentation/GMCP.md +capability: MSDP | unknown | +capability: ATCP | unknown | +capability: MXP | unknown | +capability: MSP | unknown | +capability: scripting | yes | https://beipdev.github.io/BeipMU/ +see-also: clients/mushclient +see-also: clients/potato +see-also: collaborative-roleplay +--- + +BeipMU is a MIT-licensed Windows client, actively released, and one of the few built with MUSH-style +play in mind rather than combat MUDs — multiple input windows, spawn windows, and a text engine that +expects long paragraphs. Scripting is JavaScript by default, with other ActiveScript engines +available. + +## Accessibility + +The output window implements Windows' `IAccessible` interface, added deliberately as a step toward +usability for visually impaired players, and there is a **Speak** trigger action for text-to-speech. +No particular screen reader is named anywhere, and there is no accessibility chapter in the +documentation. + +One caution if you go looking: a page in the project's own documentation still says BeipMU cannot +use speech synthesis. That page is out of date — the changelog and the maintainer's own issue +comments both post-date it. + +## Two easy mistakes about this client + +**BeipMU implements MCMP, not MSP.** They are different protocols with similar names and similar +purposes, and reading one as the other would put a claim in this table that nobody made. The MSP row +therefore says unknown. + +**It supports Pueblo, not MXP.** Pueblo is the older HTML-in-a-MUD scheme and MXP is the later one; +BeipMU documents basic Pueblo styles and clickable links. MXP was not established either way. diff --git a/content/reference/client-blightmud.md b/content/reference/client-blightmud.md new file mode 100644 index 0000000..62a922c --- /dev/null +++ b/content/reference/client-blightmud.md @@ -0,0 +1,47 @@ +--- +kind: client +slug: blightmud +title: Blightmud +summary: A modern terminal client in Rust, with Lua scripting, built-in text-to-speech and a screen-reader mode that announces itself to the server. +home: https://github.com/Blightmud/Blightmud +platform: Linux +platform: macOS +platform: Windows (WSL only) +capability: screen reader | yes | https://github.com/Blightmud/Blightmud +capability: TLS | yes | https://github.com/Blightmud/Blightmud +capability: UTF-8 | yes | https://github.com/Blightmud/Blightmud +capability: MCCP | yes | https://github.com/Blightmud/Blightmud +capability: GMCP | yes | https://github.com/Blightmud/Blightmud +capability: MSDP | yes | https://github.com/Blightmud/Blightmud +capability: ATCP | unknown | +capability: MXP | unknown | +capability: MSP | unknown | +capability: scripting | yes | https://github.com/Blightmud/Blightmud +see-also: clients/tintin +see-also: clients/mudlet +see-also: protocols/ttype +--- + +Blightmud is a terminal client written in Rust, GPL 3, and among the most actively released clients +in this section. Scripting is Lua. It is terminal-only: there is no native Windows build, and +Windows users run it under WSL. + +## Accessibility + +Blightmud has three distinct pieces here, which is more than a single row can carry: + +- A **screen-reader-friendly mode** (`--reader-mode`, or the `reader_mode` setting) that changes the + terminal UI to something a reader can follow. It does not support the status area. +- **Built-in text-to-speech**, as an optional compile, with a Lua API a script can use — including a + `tts.gag()` for suppressing a matched line from being spoken. The documentation is candid that + running its TTS alongside a screen reader is not always a happy combination. +- **Automatic MTTS advertisement**: in reader mode or with TTS enabled, it adds + `MTTS_SCREEN_READER` to what it tells the server about itself, so a game that cares can adapt. + +As with TinTin++, no particular screen reader is named, so this is a documented mode rather than +tested compatibility with a product. + +## Where the table says unknown + +**MXP**, **MSP** and **ATCP** appear nowhere in the project's README or its bundled help. **MCCP** is +documented as v2; whether v1 is also handled we did not establish. diff --git a/content/reference/client-mudlet.md b/content/reference/client-mudlet.md new file mode 100644 index 0000000..b0dd62a --- /dev/null +++ b/content/reference/client-mudlet.md @@ -0,0 +1,55 @@ +--- +kind: client +slug: mudlet +title: Mudlet +summary: Cross-platform, Lua-scripted, and the client with the most thoroughly documented screen-reader support in this section. +home: https://www.mudlet.org/ +platform: Windows +platform: macOS +platform: Linux +capability: screen reader | yes | https://wiki.mudlet.org/w/Manual:Screen_Readers +capability: TLS | yes | https://wiki.mudlet.org/w/Manual:Supported_Protocols +capability: UTF-8 | yes | https://wiki.mudlet.org/w/Manual:Supported_Protocols +capability: MCCP | unknown | +capability: GMCP | yes | https://wiki.mudlet.org/w/Manual:Supported_Protocols +capability: MSDP | yes | https://wiki.mudlet.org/w/Manual:Supported_Protocols +capability: ATCP | yes | https://wiki.mudlet.org/w/Manual:Supported_Protocols +capability: MXP | yes | https://wiki.mudlet.org/w/Manual:Supported_Protocols +capability: MSP | yes | https://wiki.mudlet.org/w/Manual:Supported_Protocols +capability: scripting | yes | https://github.com/Mudlet/Mudlet +see-also: clients/blightmud +see-also: clients/tintin +see-also: protocols/gmcp +see-also: connecting +--- + +Mudlet is a graphical client with a mapper, a package system and a Lua API that most of its own +feature set is written against. It is GPL, actively released, and the usual recommendation for +someone starting on a modern combat MUD. + +## Accessibility + +This is the client with the strongest documented case in this section, and it is worth spelling out +what "documented" means here, because it is unusual. + +Mudlet has a **manual chapter on screen readers**, per-operating-system pages naming Narrator, NVDA +and JAWS on Windows, Orca on Linux and VoiceOver on macOS, an in-client `mudlet access on` command, +and an option to announce incoming game text through the reader. It also has a setting that +advertises screen-reader use to the server over MTTS, so a game can adapt if it wants to. + +It is also candid about where it does not work well: its own Windows page says JAWS does not read +the output window the way other readers do, and recommends Narrator or NVDA instead. A project that +publishes the case where its accessibility support is impractical is giving you better information +than one that publishes a tick. + +## Where the table says unknown + +**MCCP.** Mudlet's source implements MCCP v1 and v2, but the manual's supported-protocols page does +not list it, and this section's rule is that a capability claim cites the project's own +documentation. Reading a constant out of a header is not the same act, so the cell says unknown. + +## Note on encoding + +Mudlet's default server-data encoding is ASCII rather than UTF-8, and CHARSET negotiation arrived +in 4.10. If a game's text comes out wrong on a fresh profile, that setting is the first place to +look. diff --git a/content/reference/client-mushclient.md b/content/reference/client-mushclient.md new file mode 100644 index 0000000..63caba5 --- /dev/null +++ b/content/reference/client-mushclient.md @@ -0,0 +1,52 @@ +--- +kind: client +slug: mushclient +title: MUSHclient +summary: The long-established Windows client. Five scripting languages, a plugin architecture that most of its protocol support lives in, and a release history that has slowed. +home: https://www.mushclient.com/ +platform: Windows +platform: Linux (Wine) +capability: screen reader | unknown | +capability: TLS | unknown | +capability: UTF-8 | unknown | +capability: MCCP | yes | https://www.mushclient.com/mushclient/mccp.htm +capability: GMCP | unknown | +capability: MSDP | unknown | +capability: ATCP | yes | https://www.mushclient.com/gmcp +capability: MXP | yes | https://www.mushclient.com/mushclient/doc/general/features.html +capability: MSP | yes | https://github.com/nickgammon/mushclient/blob/master/plugins/msp.xml +capability: scripting | yes | https://www.mushclient.com/mushclient/doc/general/features.html +see-also: clients/mudlet +see-also: clients/potato +see-also: protocols/mccp +--- + +MUSHclient is Nick Gammon's Windows client, MIT-licensed, and for a long stretch the default answer +for anyone on Windows. It scripts in Lua, VBScript, JScript, PerlScript and Python, and much of what +it does is carried by plugins rather than by the core — which is a genuine architectural choice and +also the reason several rows above are harder to answer than they look. + +The last tagged release is **5.06, from March 2019**. The repository is still being committed to, +and there are release notes for a 5.07 that has not shipped. + +## Why so many rows say unknown + +Every one of them is a case where the honest answer is "we could not establish it", and the reasons +differ: + +- **GMCP** — the project's own page on it presents an *example* plugin you could write, not a + feature the client has. That is different from shipping support, so the cell is unknown rather + than yes. +- **TLS** — the documented method is an external `stunnel` process. A commit adding OpenSSL-backed + TLS landed on the master branch in 2026 and is not in any release, so there is nothing a user can + install today that we can point at. +- **UTF-8** — CHARSET negotiation appears in the unreleased 5.07 notes and nowhere we could find in + a shipped version's documentation. +- **MSDP** — nothing either way. +- **Screen reader** — a text-to-speech plugin using Windows SAPI ships with the client, and that is + not the same thing as screen-reader support. There is no accessibility section in the manual, and + the author has described in his own forum why the output window is hard for a reader to work + with: it has no concept of a current line. We could not establish an answer, so the table does not + give one. + +None of these is a *no*. Several may well be yes and we could not show it. diff --git a/content/reference/client-potato.md b/content/reference/client-potato.md new file mode 100644 index 0000000..69ef1b9 --- /dev/null +++ b/content/reference/client-potato.md @@ -0,0 +1,46 @@ +--- +kind: client +slug: potato +title: Potato MUSHclient +summary: A cross-platform Tcl/Tk client written for MUSH players. Good encoding support, and a documentation set that says nothing at all about most protocols. +home: https://www.potatomushclient.com/ +platform: Windows +platform: Linux +platform: macOS +capability: screen reader | unknown | +capability: TLS | yes | https://github.com/potatomushclient/potato/wiki/ConfigureWorldsBasics +capability: UTF-8 | yes | https://github.com/potatomushclient/potato/wiki/Features +capability: MCCP | unknown | +capability: GMCP | unknown | +capability: MSDP | unknown | +capability: ATCP | unknown | +capability: MXP | unknown | +capability: MSP | unknown | +capability: scripting | yes | https://github.com/potatomushclient/potato/wiki/FAQs +see-also: clients/beipmu +see-also: clients/mushclient +see-also: collaborative-roleplay +--- + +Potato is a Tcl/Tk client built for MUSH play — multiple worlds, spawn windows, and a set of +defaults that assume you are typing poses rather than combat commands. It runs on Windows, Linux and +macOS from the same source, with the macOS builds usually a version or two behind. + +It negotiates character encoding and speaks full Unicode, which for the MUSH side of the hobby is +the capability that matters most in practice. + +Note one documented limitation: it supports connecting to a port that is SSL from the start, and its +own configuration page says STARTTLS-style negotiated SSL is **not** supported. + +## Why six rows say unknown + +We searched the project's home page, its downloads page, all 103 of its wiki help files and its +entire source tree for GMCP, MSDP, MCCP, MXP, MSP and ATCP. There is no documented statement about +any of them. There is *code* that touches some of them, and this section does not turn code into a +capability claim — a table that says "yes" on the strength of a constant in a header is making a +promise the project never made. + +The screen-reader row is the same answer reached the same way: a case-insensitive sweep for "screen +reader", "text-to-speech", NVDA, JAWS, VoiceOver, "accessibility", "visually impaired" and "blind" +across everything the project publishes returned nothing at all. That is not a finding about the +software. diff --git a/content/reference/client-tintin.md b/content/reference/client-tintin.md new file mode 100644 index 0000000..29db3ae --- /dev/null +++ b/content/reference/client-tintin.md @@ -0,0 +1,52 @@ +--- +kind: client +slug: tintin +title: TinTin++ +summary: A terminal client with its own scripting language, on every platform including phones, and a documented screen-reader mode. +home: https://tintin.mudhalla.net/ +platform: Linux +platform: macOS +platform: Windows +platform: Android +platform: iOS +capability: screen reader | yes | https://tintin.mudhalla.net/manual/screen_reader.php +capability: TLS | yes | https://github.com/scandum/tintin +capability: UTF-8 | yes | https://github.com/scandum/tintin +capability: MCCP | yes | https://tintin.mudhalla.net/ +capability: GMCP | yes | https://tintin.mudhalla.net/manual/event.php +capability: MSDP | yes | https://tintin.mudhalla.net/manual/msdp.php +capability: ATCP | unknown | +capability: MXP | unknown | +capability: MSP | unknown | +capability: scripting | yes | https://github.com/scandum/tintin +see-also: clients/blightmud +see-also: clients/mudlet +see-also: protocols/msdp +see-also: protocols/ttype +--- + +TinTin++ is a command-line client, GPL 3, actively released, and it runs in more places than +anything else here — including Android and iOS. Its scripting language is its own, terse, and +capable of a great deal; a substantial amount of what other clients do in the GUI is a `#config` +line here. + +The same author maintains the protocol specifications for **MSSP** and **MSDP**, which is why so +many of the protocol pages in this section cite the same site. + +## Accessibility + +TinTin++ has a dedicated manual page for **screen reader mode** (`#config screen reader on`, or +`-s` at startup). Enabling it does two things: it removes or alters visual elements that make no +sense read aloud, and it reports screen-reader use to the server through +[MTTS](/reference/protocols/ttype), so a game can adapt its own output. + +That is a documented mode, not a claim of testing with a particular reader — no product is named on +the page. It is meaningfully weaker evidence than a client that names the readers it works with, and +meaningfully stronger than nothing. + +## Where the table says unknown + +**MXP** and **MSP** both have community scripts on the project's site, and a script is not the +client supporting a protocol — the MXP one says outright that it may not work on every MUD. Native +support for either was not established. **ATCP** we found nothing on either way; note that ATCP is +largely superseded by GMCP, which TinTin++ does support. diff --git a/content/reference/client-tinyfugue.md b/content/reference/client-tinyfugue.md new file mode 100644 index 0000000..e0dda40 --- /dev/null +++ b/content/reference/client-tinyfugue.md @@ -0,0 +1,45 @@ +--- +kind: client +slug: tinyfugue +title: TinyFugue +summary: The classic UNIX terminal client. Upstream has not released since 2007; a maintained fork carries it forward. +home: https://tinyfugue.sourceforge.net/ +platform: Linux +platform: macOS +platform: BSD +capability: screen reader | unknown | +capability: TLS | yes | https://tinyfugue.sourceforge.net/ +capability: UTF-8 | unknown | +capability: MCCP | yes | https://tinyfugue.sourceforge.net/ +capability: GMCP | unknown | +capability: MSDP | unknown | +capability: ATCP | unknown | +capability: MXP | unknown | +capability: MSP | unknown | +capability: scripting | yes | https://tinyfugue.sourceforge.net/ +see-also: clients/tintin +see-also: clients/blightmud +--- + +TinyFugue — "tf" — is the terminal client a large part of the MUSH world used for two decades, with +separate panes for input and output, a macro language of its own, and a set of habits that have +outlived several of its competitors. + +**Upstream is dormant**: the last release is 5.0 beta 8, from January 2007. It still builds and it +still works. + +A maintained fork, *TinyFugue Rebirth*, is actively released and adds GMCP, ATCP, wide-character +support through ICU, and Python and Lua scripting alongside the native macro language. The table +above describes **upstream**, because that is what "TinyFugue" resolves to; if you are installing +today, the fork is worth looking at first. + +## The trap in this client's documentation + +Upstream has a documentation topic called **"non-visual mode"**. It is not about assistive +technology — it concerns keeping input confined to the bottom line — and it mentions no screen +reader, no speech and no blind users anywhere. A capability table assembled by keyword search would +turn that filename into a yes. This one says unknown, because that is what the documentation +supports. + +UTF-8 is the same shape of answer: the documented encoding support is for 8-bit ISO 8859 character +sets, and we found no upstream statement about UTF-8 either way. diff --git a/content/reference/client-vipmud.md b/content/reference/client-vipmud.md new file mode 100644 index 0000000..9f46dc1 --- /dev/null +++ b/content/reference/client-vipmud.md @@ -0,0 +1,43 @@ +--- +kind: client +slug: vipmud +title: VIP Mud +summary: A commercial Windows client built for blind players from the ground up. It names seven screen readers — and publishes almost nothing about its protocol support. +home: https://www.gmagames.com/vipmud.shtml +platform: Windows +capability: screen reader | yes | https://www.gmagames.com/vipmud.shtml +capability: TLS | unknown | +capability: UTF-8 | unknown | +capability: MCCP | unknown | +capability: GMCP | unknown | +capability: MSDP | unknown | +capability: ATCP | unknown | +capability: MXP | unknown | +capability: MSP | yes | https://www.gmagames.com/vipmud.shtml +capability: scripting | yes | https://www.gmagames.com/vipmud.shtml +see-also: clients/mudlet +see-also: clients/blightmud +--- + +VIP Mud is the one client in this section whose *entire* design premise is accessibility. It is +commercial — thirty dollars, with a thirty-day full trial after which it keeps working with a +reduced feature set — and it is a Windows program. + +It is the strongest accessibility claim here by a distance, and unusually it is specific. The +product page names **JAWS, Window-Eyes, System Access, NVDA, Cobra, SuperNova/Hal and Microsoft +SAPI** as working out of the box, and describes features that only make sense if you have thought +hard about the problem: different voices per window and per output type, gagging spam from speech +while still showing it, and several methods of suppressing ASCII art — which is the single most +hostile thing a MUD sends to a screen reader. + +## Why the rest of the table is empty + +Because the vendor publishes a marketing page and not a manual. Nothing on it mentions GMCP, MSDP, +MCCP, MXP, ATCP, TLS or character encoding; it describes the product as "a Telnet-based client" and +leaves it there. **Nine unknowns in a row is not a verdict on the software.** It is what a matrix +looks like when the only available source is one page, and publishing it as nine noes would be a +lie about a product that may well do all of it. + +Two further things we could not establish: any release date for the current version, and whether it +is still under active development — the vendor was acquired in February 2025, and the product page +carries a 2016 copyright. diff --git a/content/reference/codebase-aresmush.md b/content/reference/codebase-aresmush.md new file mode 100644 index 0000000..f44bd0f --- /dev/null +++ b/content/reference/codebase-aresmush.md @@ -0,0 +1,33 @@ +--- +kind: codebase +slug: aresmush +title: AresMUSH +summary: A modern roleplay server written in Ruby, with a web front end and scene tools built in rather than softcoded. +codebase: AresMUSH +home: https://aresmush.com/ +see-also: collaborative-roleplay +see-also: codebases/pennmush +see-also: codebases/evennia +--- + +AresMUSH is the newest server in wide use aimed squarely at **collaborative roleplay**, and it takes +a different position from the TinyMUSH line it succeeds. Where a PennMUSH game builds its scene +system, its character sheets and its job queue out of softcode written by whoever was around, Ares +ships those as features and expects a game's staff to configure rather than program them. + +It comes with a **web portal** — character wikis, scene logs, forums and the game itself, all +reachable from a browser — which for a genre where people read the logs afterwards is a substantial +difference in kind rather than in degree. + +Configuration is in YAML; extensions are Ruby plugins. There is no in-game programming language for +players, which is the trade: less rope, less rope-related injury, and less of the improvisational +building culture that the MUSH line is named for. + +## What it looks like from outside + +No MSSP. It answers a pre-login `WHO`, and the answer is a **per-player list** rather than a bare +number, which our parser counts by structure. No telnet options were negotiated on the game we +measured. + +If you are choosing between this and PennMUSH for a new roleplay game, the question is roughly +whether you want a system you configure or a system you write. diff --git a/content/reference/codebase-circlemud.md b/content/reference/codebase-circlemud.md new file mode 100644 index 0000000..f2aace6 --- /dev/null +++ b/content/reference/codebase-circlemud.md @@ -0,0 +1,27 @@ +--- +kind: codebase +slug: circlemud +title: CircleMUD +summary: The DikuMUD derivative that was documented well enough to teach with. Enormously forked; still running. +codebase: CircleMUD +home: https://www.circlemud.org/ +see-also: codebases/dikumud +see-also: codebases/tbamud +see-also: codebases/rom +--- + +CircleMUD is a DikuMUD derivative whose distinguishing feature was never a game mechanic: it was the +**documentation**. Jeremy Elson's release was clean, commented and accompanied by a coding guide, +and as a result it became the codebase people learned C on, ran a MUD from, and forked without +needing to reverse-engineer anything first. + +The consequence is that a very large number of running games are Circle derivatives several +generations removed, often without the word appearing anywhere a player would see it. + +Development of Circle proper is long finished; **tbaMUD** is the continuation, and a Circle game +being maintained today is usually being maintained as tbaMUD. + +## What it looks like from outside + +MSSP, answered on request. No `WHO` at the login screen — the Diku family generally does not offer +one, which is why a directory that only reads login screens sees nothing here. diff --git a/content/reference/codebase-cobramush.md b/content/reference/codebase-cobramush.md new file mode 100644 index 0000000..98da14c --- /dev/null +++ b/content/reference/codebase-cobramush.md @@ -0,0 +1,25 @@ +--- +kind: codebase +slug: cobramush +title: CobraMUSH +summary: A PennMUSH fork with its own division and power model. Small deployment, still answering. +codebase: CobraMUSH +home: https://cobramush.org/ +see-also: codebases/pennmush +see-also: codebases/rhostmush +--- + +CobraMUSH forked from PennMUSH and added a *division* model — a hierarchy of administrative +authority with delegable powers, in place of the flat wizard/royalty distinction its parent uses. +Games that want to hand out slices of staff authority without handing out everything are its +constituency. + +Softcode written for PennMUSH mostly runs, and the differences concentrate in exactly the area the +fork was about. + +## What it looks like from outside + +No MSSP, a working pre-login `WHO`, and no telnet options negotiated at all on the game we +measured. That last part is not a criticism: a server that negotiates nothing is a server that +cannot get negotiation wrong, and plain text over a plain socket is the thing every client in this +hobby handles. diff --git a/content/reference/codebase-coffeemud.md b/content/reference/codebase-coffeemud.md new file mode 100644 index 0000000..2087cd3 --- /dev/null +++ b/content/reference/codebase-coffeemud.md @@ -0,0 +1,29 @@ +--- +kind: codebase +slug: coffeemud +title: CoffeeMUD +summary: A MUD server in Java, with the largest MSSP report of anything we have probed and an unusually broad protocol surface. +codebase: CoffeeMUD +home: https://www.coffeemud.net/ +see-also: codebases/dikumud +see-also: protocols/mssp +--- + +CoffeeMUD is a Java MUD server with an unusually wide feature surface — it ships with its own web +server, mail, forums and a large class and skill system, and it is one of the few servers in the +hobby not written in C. + +It is actively maintained, which by the standards of this part of the catalogue is worth saying out +loud. + +## What it looks like from outside + +MSSP and **MCCP2**, and CoffeeMUD is one of only three servers out of twenty we tried that also +answered the *plaintext* `MSSP-REQUEST` form — a variant that predates the telnet option and is +still occasionally seen. + +Its MSSP report is the largest we have measured: **47 fields**, including `PORT` reported nine +separate times for nine separate ports. That is not a malformation. MSSP variables are lists, and a +crawler that flattens a multi-valued `PORT` into one string produces the integer `80234201` out of +`"80" "23" "4201"` — which is a bug this project shipped and fixed, and the reason the parser here +keeps values as lists throughout. diff --git a/content/reference/codebase-dikumud.md b/content/reference/codebase-dikumud.md new file mode 100644 index 0000000..8d41029 --- /dev/null +++ b/content/reference/codebase-dikumud.md @@ -0,0 +1,35 @@ +--- +kind: codebase +slug: dikumud +title: DikuMUD +summary: The root of the combat-MUD family. Levels, classes, equipment and area files — and a licence that shaped a generation of derivatives. +codebase: DikuMUD +home: https://dikumud.com/ +see-also: codebases/circlemud +see-also: codebases/rom +see-also: codebases/smaug +see-also: mush-mud-muck-moo +--- + +DikuMUD, written at Datalogisk Institut at the University of Copenhagen and released in 1991, is the +ancestor of most of what people mean when they say "MUD" without qualification. Levels, character +classes, hit points, mobs, equipment slots, an area file format a builder writes offline — the whole +vocabulary comes from here, and games that have never seen Diku source still inherit its shape. + +Its licence is part of the story. Diku was free to use but forbade charging for access and required +the original credits to be displayed, and that clause is why "the Diku credits" appear on the login +screen of games several forks removed from it. + +The direct descendants — **Merc**, then **ROM**, **CircleMUD**, **SMAUG**, **tbaMUD** and dozens of +others — account for a large fraction of every MUD listing that has ever existed. + +## What it looks like from outside + +The Diku family is the **MSSP** family. Where the MUSH side publishes a count through a login-screen +`WHO` and offers no MSSP at all, Diku-line servers overwhelmingly answer telnet option 70 with a +structured report, and that is where their numbers here come from. + +**MCCP2** — stream compression — is also common in this family, and it is worth knowing that a +client which negotiates it but cannot inflate the stream receives the entire connect screen as +binary noise. That was a real defect in this project's own telnet library and it is fixed; see +[MCCP](/reference/protocols/mccp). diff --git a/content/reference/codebase-evennia.md b/content/reference/codebase-evennia.md new file mode 100644 index 0000000..6504281 --- /dev/null +++ b/content/reference/codebase-evennia.md @@ -0,0 +1,33 @@ +--- +kind: codebase +slug: evennia +title: Evennia +summary: A Python framework rather than a finished game. Two Evennia games can have nothing in common but the plumbing. +codebase: Evennia +home: https://www.evennia.com/ +see-also: codebases/aresmush +see-also: collaborative-roleplay +see-also: protocols/gmcp +--- + +Evennia is a **MU\* framework**, not a game — which is the first thing to know about it and the +thing that makes comparing Evennia games to each other unhelpful. It is a Python library built on +Django and Twisted that gives you accounts, objects, rooms, commands, a persistence layer and the +network stack, and then expects you to write the game. + +The consequence is that "runs Evennia" tells you far less about a game than "runs PennMUSH" does. +There are combat MUDs on Evennia and there are roleplay games on Evennia and they share no +vocabulary. Two Evennia games may not have a single command in common. + +For a developer who already knows Python this is the shortest path from nothing to a running world, +and it is where a good share of new games since the mid-2010s have started. + +## What it looks like from outside + +Evennia offers **MSSP**, and it publishes a player count through it. On the game we measured it also +negotiated **MCCP2** — compression — which is characteristic of a stack that took its telnet +seriously. + +Because Evennia is a framework, what a given game negotiates is partly the game's decision. The +adoption figures on the protocol pages are counts of what servers actually offered us, not of what +the framework can do, and for Evennia those two are further apart than for most. diff --git a/content/reference/codebase-fluffos.md b/content/reference/codebase-fluffos.md new file mode 100644 index 0000000..a0f497e --- /dev/null +++ b/content/reference/codebase-fluffos.md @@ -0,0 +1,32 @@ +--- +kind: codebase +slug: fluffos +title: FluffOS +summary: The maintained MudOS successor, and the driver most surviving LPMud games run on. The game is written in LPC, not in C. +codebase: FluffOS +home: https://www.fluffos.info/ +see-also: codebases/dikumud +see-also: mush-mud-muck-moo +--- + +The LPMud tradition splits the world differently from Diku. There is a **driver** — a C program that +runs an object-oriented interpreter — and a **mudlib**, which is the entire game, written in **LPC** +and loaded by the driver. Rooms, combat, commands and the login sequence are all mudlib objects; the +driver knows about none of them. + +That makes an LPMud closer in spirit to a MUSH than its combat systems suggest: the game is written +in a language that lives inside the game, and two LPMuds sharing a driver may share nothing else. + +**MudOS** was the dominant driver for years; **FluffOS** is its maintained continuation and is what +a running LP game is most likely to be on today. Well-known mudlibs — Nightmare, Lima, Discworld's +own — are separate projects again. + +## What it looks like from outside + +MSSP and **MCCP2** on the FluffOS game we measured. MudOS was one of only two codebases in our +survey to answer *both* MSSP and a login-screen `WHO`, though the `WHO` it gave was a per-player +listing rather than a count. + +Because the mudlib is the game, what any particular LP game negotiates is a mudlib decision as much +as a driver one — the adoption figures on the protocol pages count what servers actually offered us, +which for this family is a weaker signal about the codebase than it is elsewhere. diff --git a/content/reference/codebase-moo.md b/content/reference/codebase-moo.md new file mode 100644 index 0000000..3c99174 --- /dev/null +++ b/content/reference/codebase-moo.md @@ -0,0 +1,29 @@ +--- +kind: codebase +slug: moo +title: MOO +summary: Object-oriented, edited entirely from inside, and as much a research and teaching platform as a game engine. +codebase: MOO +home: https://www.ipomoea.org/moo/ +see-also: mush-mud-muck-moo +see-also: codebases/muck +--- + +MOO — *MUD, Object-Oriented* — takes the "the world edits itself" idea further than anything else +in the hobby. LambdaMOO, the original server, ships a small C core and a database; essentially +everything a user experiences is written **in the MOO language, inside the running database, by the +people using it**. There is no source file for a room. + +That property gave MOOs a life outside games. Through the nineties they were used for teaching, +conferencing and research — Diversity University, BioMOO, Jay's House — and the technical +literature about MOO is disproportionately academic for a codebase in this space. + +Deployment today is small but genuinely non-zero, and the servers that remain have often been +running continuously for decades. + +## What it looks like from outside + +No MSSP, and no `WHO` we could parse on the game we measured. What it did have was a sentence in +its connect screen reading *"one of three players are active"* — which is where the spelled-out +number reader in this crawler comes from. A digits-only parser sees no count there at all, and +would have reported that game as unknown for ever. diff --git a/content/reference/codebase-muck.md b/content/reference/codebase-muck.md new file mode 100644 index 0000000..cf1cb69 --- /dev/null +++ b/content/reference/codebase-muck.md @@ -0,0 +1,28 @@ +--- +kind: codebase +slug: muck +title: MUCK +summary: A TinyMUD descendant with its own Forth-like in-game language, and a social culture distinct from the MUSH side. +codebase: MUCK +home: https://www.fuzzball.org/ +see-also: mush-mud-muck-moo +see-also: codebases/tinymush +see-also: codebases/moo +--- + +MUCK — in practice almost always **Fuzzball MUCK** — is a sibling of the MUSH line rather than a +descendant of it: both come from TinyMUD, and both put a programming language inside the game. + +The language is the visible difference. MUF (*Multi-User Forth*) is stack-based and reads nothing +like MUSH softcode; a builder fluent in one is a beginner in the other. Above it sits MPI, a smaller +inline expression language used for the things softcode would do on a MUSH. + +Culturally, MUCK is the home of a large part of the hobby's social and fandom worlds. Those games +tend to be built around presence and conversation rather than around scenes with a start and an end, +which is a real difference from the roleplay MUSH tradition and not a matter of theme. + +## What it looks like from outside + +No MSSP. A pre-login `WHO` that answers with a count. No telnet options negotiated on the game we +measured — and one detail from the survey worth keeping: its `WHO` reply ended in a trailing space +with no newline, which is the kind of thing that makes a naive parser report nothing at all. diff --git a/content/reference/codebase-pennmush.md b/content/reference/codebase-pennmush.md new file mode 100644 index 0000000..12ad2ba --- /dev/null +++ b/content/reference/codebase-pennmush.md @@ -0,0 +1,48 @@ +--- +kind: codebase +slug: pennmush +title: PennMUSH +summary: The most widely deployed MUSH server. Softcode, a long release history, and one of only two codebases in our survey that answer both MSSP and a pre-login WHO. +codebase: PennMUSH +home: https://www.pennmush.org/ +see-also: codebases/tinymux +see-also: codebases/rhostmush +see-also: codebases/cobramush +see-also: mush-mud-muck-moo +see-also: protocols/mssp +--- + +PennMUSH descends from TinyMUSH by way of a 1991 fork, and it is the server most long-running +roleplay MUSHes run. Its defining feature is **softcode**: a functional expression language, edited +from inside the game by anyone with the right bit set, in which a large fraction of any given +MUSH's behaviour is written. A PennMUSH game is not so much configured as programmed by its +players. + +Versions read as `1.8.8p0` — a major, a minor and a patchlevel — and the patchlevel moves often. +Games frequently run a version several patchlevels behind, which is unremarkable. + +## What it looks like from outside + +PennMUSH is one of only two codebases in our own 38-server survey that answered *both* routes we +probe. It offers MSSP when asked, and it answers a `WHO` typed at the login screen, and on the game +we measured the two agreed — which is rarer than it sounds, and made PennMUSH the control we tested +other servers against. + +The pre-login `WHO` matters beyond convenience: it is how the MUSH family publishes a player count +at all, since most of the rest of the family offers no MSSP whatsoever. See +[MSSP](/reference/protocols/mssp) for why that split is the reason this site probes four layers +rather than one. + +CHARSET negotiation is normal on modern PennMUSH, which is why accented names survive the trip. + +## Related servers + +PennMUSH, **TinyMUX**, **RhostMUSH** and **CobraMUSH** are four servers with a common ancestor and +a shared vocabulary — a builder who knows one can read another's softcode with effort. They are not +compatible: a database does not move between them without a conversion, and function libraries +differ in ways that matter. + +## SharpMUSH + +A .NET reimplementation aiming at PennMUSH compatibility is in development, by the same author as +this site. Nothing on this page is measured from it, and it has no games in the catalogue. diff --git a/content/reference/codebase-rhostmush.md b/content/reference/codebase-rhostmush.md new file mode 100644 index 0000000..25c689f --- /dev/null +++ b/content/reference/codebase-rhostmush.md @@ -0,0 +1,27 @@ +--- +kind: codebase +slug: rhostmush +title: RhostMUSH +summary: A MUSH server known for a deep permissions model and a large built-in function set. No MSSP; answers a pre-login WHO. +codebase: RhostMUSH +home: https://github.com/RhostMUSH/trunk +see-also: codebases/pennmush +see-also: codebases/tinymux +see-also: codebases/cobramush +--- + +RhostMUSH is the fourth of the TinyMUSH-descended servers in wide use, and the one with the most +elaborate administrative model: its permission and flag system is considerably finer-grained than +its relatives', which is the usual reason a game chooses it. + +Its built-in function library is large, and softcode written for Rhost often does not port cleanly +to PennMUSH or TinyMUX without rewriting the parts that used functions the others do not have. + +## What it looks like from outside + +No MSSP. A pre-login `WHO` that answers with a count. CHARSET is negotiated. + +That combination — no MSSP, a working `WHO` — is the MUSH family's signature, and it is why this +site probes the login screen at all. On the evidence of our own survey the MSSP and `WHO` families +are nearly disjoint: 28 codebases publish a count through MSSP, seven through `WHO`, and only two +through both. diff --git a/content/reference/codebase-rom.md b/content/reference/codebase-rom.md new file mode 100644 index 0000000..189cf4c --- /dev/null +++ b/content/reference/codebase-rom.md @@ -0,0 +1,29 @@ +--- +kind: codebase +slug: rom +title: ROM +summary: Merc's best-known descendant, and the combat engine a large share of nineties MUDs were built on. +codebase: ROM +see-also: codebases/dikumud +see-also: codebases/smaug +see-also: protocols/mccp +--- + +ROM — *Rivers of MUD* — is a derivative of **Merc**, which is itself a DikuMUD derivative, and it is +the one that stuck. Its combat model, its skill and spell system and its area format were the +starting point for an enormous number of games through the nineties and after, and ROM 2.4 in +particular is one of the most-forked pieces of source in the hobby. + +Like the rest of the Diku line it carries the original credits requirement, so a game whose lineage +you cannot otherwise establish will often name Diku, Merc and ROM on its login screen. + +## What it looks like from outside + +MSSP, CHARSET and **MCCP2**, on the game we measured. + +ROM is the server this project proved its own compression bug against. Our probe negotiated MCCP2, +the server correctly began compressing, and the telnet library we depend on never inflated the +stream — so the connect screen arrived as a wall of replacement characters and we briefly recorded +that as the game's fault. The payload decompressed cleanly with a stock zlib call, which is what +made it unambiguous. It was fixed upstream; the story is on the [MCCP](/reference/protocols/mccp) +page, because it is a good example of a defect that looks exactly like a broken game from outside. diff --git a/content/reference/codebase-smaug.md b/content/reference/codebase-smaug.md new file mode 100644 index 0000000..e2cde6b --- /dev/null +++ b/content/reference/codebase-smaug.md @@ -0,0 +1,27 @@ +--- +kind: codebase +slug: smaug +title: SMAUG +summary: A Merc derivative built for large themed worlds, with deities, planes and an area set several times the size of its parent's. +codebase: SMAUG +home: https://www.smaug.org/ +see-also: codebases/dikumud +see-also: codebases/rom +--- + +SMAUG — *Simulated Medieval Adventure multi-User Game* — came out of the Realms of Despair and is a +Merc derivative that grew rather than simplified. Where ROM tightened Merc's combat, SMAUG added: +deities, planes, more elaborate area and mob programs, a much larger stock world, and a +correspondingly larger source tree. + +Games running it tend to be the big themed ones, and several have been running continuously for +twenty-five years. + +## What it looks like from outside + +MSSP, answered on request, and nothing else negotiated on the game we measured. + +Note for anyone comparing counts: SMAUG servers in our survey reported through MSSP and did not +answer a login-screen `WHO`, so the number here is the game's own MSSP `PLAYERS` — an assertion the +server makes, taken at face value only because we could not measure it independently. The game page +labels which of the two any given figure came from. diff --git a/content/reference/codebase-tbamud.md b/content/reference/codebase-tbamud.md new file mode 100644 index 0000000..1839662 --- /dev/null +++ b/content/reference/codebase-tbamud.md @@ -0,0 +1,24 @@ +--- +kind: codebase +slug: tbamud +title: tbaMUD +summary: The maintained continuation of CircleMUD, and the usual answer to "I want to start a Diku-style MUD today". +codebase: tbaMUD +home: https://tbamud.com/ +see-also: codebases/circlemud +see-also: codebases/dikumud +--- + +tbaMUD — *The Builder Academy MUD* — picked up CircleMUD where its own development stopped and has +kept it building, patched and documented since. If somebody is starting a levels-and-combat MUD from +a known-good base rather than from their own fork, this is the base. + +It carries the Circle codebase's virtues forward: readable C, a documented area file format, and a +builder's manual that assumes you have not done this before. The Builder Academy itself runs as a +teaching game where people learn to build on it. + +## What it looks like from outside + +MSSP, answered on request. A measured zero on the game we probed — which on this site is a filled +cell rather than an empty one: we got in, we read the number, and the number was nought. That is a +different fact from a game we could not count, and the two are never merged here. diff --git a/content/reference/codebase-tinymush.md b/content/reference/codebase-tinymush.md new file mode 100644 index 0000000..e2b5564 --- /dev/null +++ b/content/reference/codebase-tinymush.md @@ -0,0 +1,38 @@ +--- +kind: codebase +slug: tinymush +title: TinyMUSH +summary: The ancestor of the MUSH line, still running games. It taught this crawler that its own negotiation bytes can break the next command it sends. +codebase: TinyMUSH +home: https://github.com/TinyMUSH/TinyMUSH +see-also: codebases/pennmush +see-also: codebases/tinymux +see-also: mush-mud-muck-moo +--- + +TinyMUSH is where the line PennMUSH, TinyMUX, RhostMUSH and CobraMUSH all descend from, and it is +still deployed. Development is quiet rather than absent. + +## What it looks like from outside + +No MSSP. A pre-login `WHO` that answers with a sentence of the form `0 Players logged in, 22 +record, no maximum.` + +## The bug it found in us + +TinyMUSH is worth a paragraph here because it is the game that exposed a defect in this site's own +crawler, and the correction is a good illustration of what "measured" is supposed to mean. + +Our probe read TinyMUSH as *count unknown* for weeks. The guess on file was that its reply had no +trailing newline. It does. Captured off the wire, the real cause was ours: **TinyMUSH does not +parse telnet at its login screen**, so the three bytes of `IAC DO MSSP` we send on connect land in +its input buffer as though somebody had typed them. The next line it reads is not `WHO` but three +control bytes followed by `WHO`, which is not a command it has — so it redisplays its connect +screen and says nothing about players. + +The probe now sends a bare newline after negotiating and discards whatever that produces, because +that output is a reaction to bytes *we* chose to send and is therefore neither the game's connect +screen nor its answer. TinyMUSH reads correctly now, and the probe finished in a third of the time. + +A directory that had not checked would have published "this game does not report its players" for +as long as it existed, and the sentence would have been about us. diff --git a/content/reference/codebase-tinymux.md b/content/reference/codebase-tinymux.md new file mode 100644 index 0000000..0c17525 --- /dev/null +++ b/content/reference/codebase-tinymux.md @@ -0,0 +1,36 @@ +--- +kind: codebase +slug: tinymux +title: TinyMUX +summary: The other big MUSH server. Softcode close enough to PennMUSH's to argue about, no MSSP at all, and a pre-login WHO that works. +codebase: TinyMUX +home: https://www.tinymux.org/ +see-also: codebases/pennmush +see-also: codebases/tinymush +see-also: codebases/rhostmush +see-also: mush-mud-muck-moo +--- + +TinyMUX is the second of the two servers most established roleplay MUSHes run, and for many +players the choice between it and PennMUSH is a matter of which one their game's staff learned +first. Versions read as `2.12` and similar. + +Like PennMUSH it descends from TinyMUSH, and its softcode is close enough that a builder moving +between the two is translating rather than relearning. The differences are real — function +libraries, some parsing corners, the `@`-command set — and are exactly the kind of thing that makes +moving a database between them a project rather than an export. + +## What it looks like from outside + +**No MSSP.** TinyMUX offers the option not at all, which puts it with AresMUSH, MUCK, RhostMUSH, +CobraMUSH and TinyMUSH on the side of the hobby that an MSSP-only directory simply cannot see. Its +player count comes from a `WHO` at the login screen, which it answers with a plain count. + +It does negotiate CHARSET, which is how it comes out ahead of most of its relatives on non-ASCII +text. + +## Where the counts come from + +If you are comparing this site's number for a TinyMUX game against another directory's, note that +we read the login-screen `WHO` and most crawlers do not. A directory built on MSSP alone reports +these games as having no count at all, or does not list them. diff --git a/content/reference/orientation-collaborative-roleplay.md b/content/reference/orientation-collaborative-roleplay.md new file mode 100644 index 0000000..959ba35 --- /dev/null +++ b/content/reference/orientation-collaborative-roleplay.md @@ -0,0 +1,63 @@ +--- +kind: orientation +slug: collaborative-roleplay +title: You want collaborative roleplay +summary: If what you are after is writing scenes with other people, this is the short path — the codebases to look for, a client that will not fight you, and what to expect on your first night. +see-also: mush-mud-muck-moo +see-also: connecting +see-also: codebases/pennmush +see-also: codebases/aresmush +see-also: codebases/evennia +see-also: clients/mudlet +--- + +By *collaborative roleplay* we mean: you and other people write a scene together, in character, in +prose, mostly in real time. Nobody rolls initiative unless the game says to. This is the part of the +hobby that the word "MUD" hides, and it is alive. + +## Look for these codebases + +The TinyMUD line is where this culture lives, because its servers are built for people who make +things rather than for people who kill things. + +- **[PennMUSH](/reference/codebases/pennmush)** and **[TinyMUX](/reference/codebases/tinymux)** — + the two big MUSH servers. Most long-running roleplay games you have heard of run one of them. +- **[AresMUSH](/reference/codebases/aresmush)** — modern, built specifically for roleplay, with a + web front end and scene logging as first-class features rather than as softcode somebody wrote. +- **[Evennia](/reference/codebases/evennia)** — a Python framework rather than a finished game. + Games built on it vary enormously; several of the newer roleplay games are Evennia. +- **[MUCK](/reference/codebases/muck)** — its own culture, heavily social, and worth knowing about + if the fandom worlds are what you are looking for. + +You can filter the catalogue by any of these: each codebase page links to the games we have measured +running it. + +## A client that will not fight you + +You do not need one — every game here speaks plain telnet, and your operating system probably has a +client already. But for scene-writing, three things matter more than they do elsewhere: + +1. **A command line that survives a long paragraph.** You will type multi-sentence poses. +2. **Logging.** You will want the scene afterwards. +3. **UTF-8.** Names have accents in them. + +See the [clients](/reference) section. If you use a screen reader, the capability tables there carry +a row for it — we could not establish an answer for every client, and where we could not, the table +says *unknown* rather than guessing. + +## What the first night is actually like + +Most roleplay games have an **application process**: you connect as a guest, read some help files, +and write a character up for staff to read. This can take a day or a fortnight. It is not +gatekeeping for its own sake — it is how a game keeps a shared setting coherent — but it does mean +"log in and play" is rarely the shape of it. + +Games with nobody on at the moment you look are normal here, and the activity heatmap on each game's +page is more useful than its live count: a game with fifteen people every evening and none at 4am is +a healthy game seen at the wrong hour. + +## What this site cannot tell you + +Whether the people are any good. Whether the staff are fair. Whether the setting is one you would +enjoy. We measure servers. There is no rating here and there will not be one — that is a deliberate +decision, not a gap. diff --git a/content/reference/orientation-connecting.md b/content/reference/orientation-connecting.md new file mode 100644 index 0000000..e45202e --- /dev/null +++ b/content/reference/orientation-connecting.md @@ -0,0 +1,46 @@ +--- +kind: orientation +slug: connecting +title: How to connect +summary: A host, a port, and telnet. What the address on a game page means and what to do with it. +see-also: mush-mud-muck-moo +see-also: protocols/tls +see-also: protocols/charset +--- + +Every game listed here answers on a **host and a port**, and the protocol underneath is telnet — +which in practice means a raw TCP connection with a small amount of optional negotiation on top. + + telnet mush.pennmush.org 4201 + +That works, and on many systems it is already installed. It is also a poor way to play: the system +`telnet` has no local echo control worth the name, no logging, no history, and it will mangle +anything above ASCII. It is the right tool for checking that a game is up and the wrong one for +spending an evening in. + +## What the address on a game page tells you + +Each game page lists the endpoints we have measured, and marks any where **TLS** was observed. A +game with a TLS port is a game you can connect to encrypted; the port number is usually different +from the plain one. + +Where a game has several ports, they are frequently the same world reached different ways rather +than different games. We list what we measured and do not guess at which is canonical. + +## Choosing a client + +The [clients](/reference) section has a page each, with a capability table. The three things worth +checking before you install anything: + +- **Does it do UTF-8?** If the game is not English-only, this will come up on your first evening. +- **Does it do TLS?** Only matters if the game offers it, but several now do. +- **If you use a screen reader, does the project document support for it?** This is the row most + often missing from client comparisons, so it is the first row of ours — and where nobody has + established an answer, it says *unknown*. + +## If nothing answers + +A game that does not answer is not necessarily gone. Games move hosts, DNS lapses, and firewalls +have opinions. This site keeps every game it has ever measured — including the ones that stopped +answering years ago — and keeps knocking weekly, so the [archive](/archive) is the place to look +before concluding anything. diff --git a/content/reference/orientation-families.md b/content/reference/orientation-families.md new file mode 100644 index 0000000..5cf7d7b --- /dev/null +++ b/content/reference/orientation-families.md @@ -0,0 +1,60 @@ +--- +kind: orientation +slug: mush-mud-muck-moo +title: MUSH, MUD, MUCK, MOO — what the words mean +summary: Four words for four traditions, none of which is a genre. What they actually tell you. +see-also: collaborative-roleplay +see-also: connecting +see-also: codebases/pennmush +see-also: codebases/evennia +--- + +Every one of these words names a **family of server software**, not a kind of game. That is the +single most useful thing to know about them, and it is why "is this a MUSH or a MUD?" is so often +answered badly: the honest answer is usually *both, and the question you meant was about the +culture*. + +## MUD + +The oldest term, and now the broadest. It began as *Multi-User Dungeon* — Bartle and Trubshaw's 1978 +game — and by the mid-nineties was the umbrella word for every text-based multiplayer world. + +Used narrowly, it means the **DikuMUD and LPMud lines**: servers built around levels, combat, +equipment and an area file describing rooms a builder wrote in advance. If somebody says "I play a +MUD" and means something specific, this is usually it. + +## MUSH + +*Multi-User Shared Hallucination*, from the TinyMUD line. The defining property is not the theme but +the **softcode**: MUSH servers ship a programming language players use from inside the game, so a +player with build permissions creates rooms, objects and behaviour without touching a source file or +restarting anything. + +That one design decision produced the culture. MUSHes tend to be sparse on automated systems and +dense on human ones — staff-run plots, written scenes, application processes — because the people +playing are also the people building. + +## MUCK + +A TinyMUD descendant like MUSH, with its own softcode (MUF, a Forth-like language) and a strong +tradition of social and furry-fandom worlds. Technically close to MUSH; culturally distinct enough +that people who play both would not describe them as the same thing. + +## MOO + +*MUD, Object-Oriented*. The purest expression of the "the game edits itself" idea: nearly everything +in a MOO is written in the MOO programming language by the people using it, from inside. LambdaMOO +is the ancestor, and MOOs have historically been as popular in education and research as in games. + +## So what should you actually ask? + +Three questions do more work than the four-letter word: + +1. **Is there combat, and is it automated?** This separates the Diku/LP line from the TinyMUD line + more reliably than any name. +2. **Who builds?** Staff-only, or anyone with a build bit? +3. **Is play scheduled or ambient?** Appointment-based scenes and posed roleplay, or log in and go? + +The listing on this site can answer part of the first question for you: the **codebase** we measured +for a game tells you which tradition its server comes from. It cannot tell you the culture, and this +page will not pretend otherwise. diff --git a/content/reference/protocol-atcp.md b/content/reference/protocol-atcp.md new file mode 100644 index 0000000..e04e47f --- /dev/null +++ b/content/reference/protocol-atcp.md @@ -0,0 +1,31 @@ +--- +kind: protocol +slug: atcp +title: ATCP +summary: GMCP's predecessor. Out-of-band data with a looser payload, largely superseded, and still negotiated by servers that never removed it. +protocol: ATCP +see-also: protocols/gmcp +see-also: protocols/msdp +see-also: clients/mudlet +--- + +ATCP — the Achaea Telnet Client Protocol — is telnet option 200, and it is where the idea of sending +structured data alongside MUD text was first widely deployed. A server sends a module name and a +payload; the client routes it. + +Its payload format is looser than [GMCP](/reference/protocols/gmcp)'s JSON, which is essentially why +GMCP replaced it. Clients that support ATCP now generally document it as deprecated and point you at +GMCP instead. + +## Why it is still here + +Because nothing breaks by leaving it on. A server that implemented ATCP in 2008 and added GMCP in +2014 usually still negotiates both, and a client that supports both will take whichever it is +offered. + +For a new implementation there is no reason to choose it. + +## What we measure + +Servers offering telnet option 200 in a handshake we observed. A low figure here is expected and is +about age rather than about anything else. diff --git a/content/reference/protocol-charset.md b/content/reference/protocol-charset.md new file mode 100644 index 0000000..5e11a2c --- /dev/null +++ b/content/reference/protocol-charset.md @@ -0,0 +1,38 @@ +--- +kind: protocol +slug: charset +title: CHARSET +summary: RFC 2066's telnet option for agreeing an encoding. The reason a game's accented names survive the trip, and the source of some subtle failures when it is absent. +protocol: CHARSET +home: https://www.rfc-editor.org/rfc/rfc2066 +see-also: protocols/ttype +see-also: connecting +see-also: codebases/tinymux +--- + +CHARSET is telnet option 42, specified in RFC 2066. One side offers a list of character sets, the +other picks one, and both then agree on how bytes map to characters. + +In practice the negotiation settles on **UTF-8** or is not held at all. The MUSH family negotiates it +noticeably more than the MUD family — TinyMUX, RhostMUSH and PennMUSH all do — which reflects a +population that writes prose with names in it. + +## What happens without it + +A client has to guess, and the usual guess is either ASCII or Latin-1. Guess ASCII and every byte +above 0x7F becomes a question mark; guess Latin-1 on a UTF-8 server and every accented character +becomes two pieces of punctuation. Both failures look like the game's fault and are not. + +For a crawler this bites in a specific place. Our own telnet library defaults its current encoding to +ASCII, and that default is not inert — it is what every byte is decoded with, for every server that +never negotiates CHARSET, which is most of them. We seed it deliberately for that reason. + +## The one place CHARSET does not reach + +MSSP field names and values are decoded as ASCII regardless of what CHARSET settled on, because a +subnegotiation is a command rather than text and the specification scopes CHARSET to text. That is +arguably conformant and it is lossy: a game whose MSSP `NAME` is `Café Noir` reports `Caf? Noir`, and +the original bytes are gone before anything we control sees them. + +If you see a mangled character in a declared field on this site and not in the game's own output, +that is why, and it is not recoverable from our side. diff --git a/content/reference/protocol-gmcp.md b/content/reference/protocol-gmcp.md new file mode 100644 index 0000000..a9a06a2 --- /dev/null +++ b/content/reference/protocol-gmcp.md @@ -0,0 +1,41 @@ +--- +kind: protocol +slug: gmcp +title: GMCP +summary: The Generic Mud Communication Protocol — structured JSON messages alongside the text, and the out-of-band channel most modern clients build against. +protocol: GMCP +home: https://www.mudhalla.net/tintin/protocols/gmcp/ +see-also: protocols/msdp +see-also: protocols/atcp +see-also: clients/mudlet +--- + +GMCP is telnet option 201. Once negotiated, the server can send **structured data out of band**: +a package name and a JSON payload, arriving in the same stream as the text but not part of it. + +`Char.Vitals { "hp": 412, "maxhp": 500 }` is the canonical example. A client can drive a health bar +from that without scraping the prose for numbers, which is the entire point — a status display built +on pattern-matching the text breaks the day a game changes its prompt, and one built on GMCP does +not. + +The package namespace is conventional rather than standardised. `Char`, `Room`, `Comm` and `Client` +are widely used; beyond that, games invent what they need, and a client generally has to be told what +a given game sends. + +## Why it displaced ATCP + +GMCP is the successor to [ATCP](/reference/protocols/atcp), which did the same job with a +looser payload format. JSON was the improvement, and the migration was largely complete by the +mid-2010s. A game supporting both is not unusual; a new game supporting only ATCP would be. + +## What we measure + +A game counts here when **its server offered GMCP in a handshake we observed**. That is a different +claim from a game's MSSP saying `GMCP 1`, which is what most protocol tables in this hobby are built +on, and the two disagree regularly. + +One measurement note from our own history: for a period we could not see GMCP on servers that also +negotiated [MCCP](/reference/protocols/mccp), because our telnet library negotiated compression +without inflating the stream and everything after the compression marker was noise to us. At least +one server in our survey turned out to speak GMCP all along. If a figure on this page looks low for +a family you know well, that class of defect is the first thing to suspect — in us, not in them. diff --git a/content/reference/protocol-mccp.md b/content/reference/protocol-mccp.md new file mode 100644 index 0000000..42fb5fa --- /dev/null +++ b/content/reference/protocol-mccp.md @@ -0,0 +1,40 @@ +--- +kind: protocol +slug: mccp +title: MCCP +summary: Stream compression. Cheap, widely deployed, and the protocol that produced the most instructive bug in this project's history. +protocol: MCCP +home: https://www.mudhalla.net/tintin/protocols/mccp/ +see-also: codebases/rom +see-also: codebases/dikumud +see-also: protocols/gmcp +--- + +MCCP compresses the server-to-client stream with zlib. Version 1 is telnet option 85 and is +effectively historical; **version 2** is option 86 and is what modern servers negotiate. After the +server sends `IAC SB MCCP2 IAC SE`, every byte that follows is part of one continuous zlib stream. + +It is a real saving on a text protocol — MUD output compresses extremely well — and it is common in +the Diku and LP families, where roughly a third of the codebases we surveyed negotiate it. + +## The failure mode, and why it matters here + +A client that negotiates MCCP2 and then does not inflate the stream receives **binary garbage from +the compression marker onward**. Not an error, not a disconnection: the connect screen arrives as a +wall of replacement characters, and everything after it — the `WHO` reply, any later MSSP, the whole +session — is lost. + +This is not hypothetical. Our own telnet library did exactly that. It negotiated the option, fired +its "compression enabled" callback, and never inflated a byte. The payload decompressed cleanly with +a stock zlib call, which is what made it unambiguous that the servers were correct and we were not. +Thirteen of the thirty-eight codebases in our survey were affected, and for the duration we could not +observe what those servers negotiated *after* compression started — so our record of their +capabilities understated them. + +It was fixed upstream. A follow-on defect — the inflater being re-created per read rather than kept +for the connection, which fails partway through a large connect screen — is filed and open, and +affects the tail of the largest screens. + +Two things a reader should take from this. **A protocol figure on this page is a measurement of our +crawler as much as of the hobby**, and where we know it has been wrong we say so. And if you are +writing a client: negotiating MCCP is easy and inflating it correctly is where the work is. diff --git a/content/reference/protocol-msdp.md b/content/reference/protocol-msdp.md new file mode 100644 index 0000000..fae3e2a --- /dev/null +++ b/content/reference/protocol-msdp.md @@ -0,0 +1,29 @@ +--- +kind: protocol +slug: msdp +title: MSDP +summary: The Mud Server Data Protocol — the same job as GMCP, done with a compact binary encoding and a discovery mechanism GMCP lacks. +protocol: MSDP +home: https://www.mudhalla.net/tintin/protocols/msdp/ +see-also: protocols/gmcp +see-also: clients/tintin +see-also: clients/blightmud +--- + +MSDP is telnet option 69, and it solves the same problem as [GMCP](/reference/protocols/gmcp): +sending structured data alongside the text so a client does not have to scrape prose for numbers. + +The differences are two. MSDP's encoding is **binary and compact** — variables and values are marked +with single control bytes rather than wrapped in JSON — and MSDP defines a **discovery** +conversation: a client can ask `LIST` for `COMMANDS`, `REPORTABLE_VARIABLES` and so on, and be told +what a given game supports. GMCP has no equivalent, which is why a GMCP client generally has to be +configured per game. + +In practice GMCP won on adoption and MSDP persists in the servers and clients that implemented it, +often alongside GMCP. + +## What we measure + +A game counts here when its server offered MSDP in a handshake we observed. As with every figure in +this section, that is a positive observation and the remainder is not its opposite — a game not +counted may not implement MSDP, or may simply not have had its handshake read by us yet. diff --git a/content/reference/protocol-msp.md b/content/reference/protocol-msp.md new file mode 100644 index 0000000..8eb04aa --- /dev/null +++ b/content/reference/protocol-msp.md @@ -0,0 +1,38 @@ +--- +kind: protocol +slug: msp +title: MSP +summary: The MUD Sound Protocol — the server names a sound file and the client plays it. Old, simple, and easy to confuse with two other things. +protocol: MSP +home: https://www.zuggsoft.com/zmud/msp.htm +see-also: protocols/mxp +see-also: clients/vipmud +--- + +MSP lets a server ask a client to play a sound: a bracketed directive naming a file, a volume, a +repeat count and a URL to fetch it from if the client does not have it. It negotiates on telnet +option 90, and it can also be sent in-band in the text stream by servers that never negotiate +anything. + +It is genuinely old and genuinely still used — ambient sound in a text game is a bigger effect than +it sounds like, and for players using a client's audio cues rather than its display it is more than +decoration. + +## Three things it is not + +The client tables in this section had to be careful here, and it is worth writing down why: + +- **MCMP** — the Mud Client Media Protocol — is a different protocol doing a similar job. At least + one client implements MCMP and not MSP, and reading one as the other would put a claim in a table + that nobody made. +- **A client's own "play a sound" scripting call** is not MSP. It plays a local file when a script + says so; MSP is a server telling a client what to play. +- **Bundled-plugin support is worth stating as such.** One client's MSP support ships as a plugin + that explicitly does no telnet negotiation, which works on servers that send MSP in band and not on + servers that expect to negotiate it. + +## What we measure + +Servers offering telnet option 90. Because MSP is frequently sent in band without negotiation, this +figure understates deployment by an amount we cannot estimate — which is a limitation of what a +handshake can see, and not a finding about the protocol. diff --git a/content/reference/protocol-mssp.md b/content/reference/protocol-mssp.md new file mode 100644 index 0000000..806be9f --- /dev/null +++ b/content/reference/protocol-mssp.md @@ -0,0 +1,59 @@ +--- +kind: protocol +slug: mssp +title: MSSP +summary: The Mud Server Status Protocol — how a game tells a crawler about itself. Everything it reports is declared, not measured, and this site keeps the two apart. +protocol: MSSP +home: https://www.mudhalla.net/tintin/protocols/mssp/ +see-also: protocols/gmcp +see-also: codebases/dikumud +see-also: codebases/pennmush +--- + +MSSP is telnet option 70. A crawler sends `IAC DO MSSP`; a server that supports it replies with a +table of name/value pairs describing itself — name, player count, codebase, uptime, hostname, port, +genre, and whatever else it cares to publish. + +It is the closest thing this hobby has to a machine-readable directory entry, and it is the reason +several directories exist at all. + +## Everything in an MSSP report is an assertion + +This is the point on which this site differs from every incumbent. An MSSP report is the game +*telling you* about itself. `GMCP 1` in an MSSP table means somebody typed `1` into a configuration +file, possibly in 2011. It is not evidence that the server offers GMCP, and the two disagree often +enough to be interesting. + +So MSSP-derived facts are labelled **declared** here, and where we can measure the same fact — a +capability, by seeing whether the option is actually negotiated — both are shown, side by side, with +an age on each. A game whose MSSP has declared GMCP for six years and has never once offered it in a +handshake is a fact worth knowing, and there is nowhere else you can find it. + +The one field we deliberately do not credit at all is `CREATED`. It is a single hand-typed line, and +crediting it toward anything would make that thing trivially gameable. + +## Who answers it + +MSSP is the **Diku and LP** answer. In our own 38-codebase survey, 28 published a player count +through MSSP and seven through a login-screen `WHO`, and only two did both — the two families are +very nearly disjoint. AresMUSH, TinyMUX, MUCK, RhostMUSH, CobraMUSH and TinyMUSH offer no MSSP +whatsoever. + +That is the empirical case for probing four layers rather than one: **a crawler built on MSSP alone +cannot see most of the MUSH family**, which is a large part of the hobby and most of this site's +intended audience. + +## Ask, do not wait + +A great many servers that fully support MSSP will never volunteer it — they answer `IAC DO MSSP` and +say nothing otherwise. A crawler that opens with `IAC WILL NAWS` and waits therefore reports those +games as publishing nothing, which is a claim about the server made out of the crawler's own silence. +We send `IAC DO MSSP` on connect. + +## The plaintext form + +There is an older variant in which a client sends the literal line `MSSP-REQUEST` at the login +screen. We measured it: of twenty games tried, three answered — and all three also answered telnet +option 70, so it reached nothing the option did not already reach. Eight servers read the request as +a **character name** and said so, spending one of the login attempts a stranger is allowed. We do not +send it. diff --git a/content/reference/protocol-mxp.md b/content/reference/protocol-mxp.md new file mode 100644 index 0000000..63205f6 --- /dev/null +++ b/content/reference/protocol-mxp.md @@ -0,0 +1,39 @@ +--- +kind: protocol +slug: mxp +title: MXP +summary: The MUD eXtension Protocol — HTML-like markup in the text stream, giving clickable links, images and forms. Widely specified, unevenly implemented. +protocol: MXP +home: https://www.zuggsoft.com/zmud/mxp.htm +see-also: protocols/pueblo +see-also: clients/mushclient +see-also: clients/mudlet +--- + +MXP embeds a small, HTML-like markup language in the text a server sends: `` for a clickable +command, `` for a link, colour and font elements, and a mechanism for a server to define its +own tags. It negotiates on telnet option 91. + +Its design problem is inherent and interesting: the markup travels in the same stream as the text, +so a server has to be careful about text that *looks* like markup, and a client has to be careful +about what it will render. MXP defines security levels for exactly this reason — a tag arriving in a +line of chat from another player is not the same as a tag the server emitted itself. + +## Clickability is the reason people want it + +Most of what MXP is actually used for is turning `north` and item names into things you can click. +For a new player that is a substantial difference, and it is why the protocol keeps being +implemented despite its complexity. + +## Pueblo is the other one + +[Pueblo](/reference/protocols/pueblo) predates MXP and does a similar job with a different, more +literally HTML-shaped approach. A client that supports one frequently does not support the other, +and the two are easy to confuse when reading a feature list — which is a mistake we have had to be +careful about in the client tables in this section. + +## What we measure + +Servers offering telnet option 91 in a handshake we observed. MXP is less commonly negotiated than +the out-of-band protocols, partly because much of its value is realised by servers that simply emit +the markup and hope, without negotiating at all — which we cannot see. diff --git a/content/reference/protocol-pueblo.md b/content/reference/protocol-pueblo.md new file mode 100644 index 0000000..337617e --- /dev/null +++ b/content/reference/protocol-pueblo.md @@ -0,0 +1,32 @@ +--- +kind: protocol +slug: pueblo +title: Pueblo +summary: The older HTML-in-a-MUD scheme, from the client of the same name. Still supported by MUSH-side clients, and routinely confused with MXP. +protocol: PUEBLO +home: https://pueblo.sourceforge.net/ +see-also: protocols/mxp +see-also: clients/beipmu +--- + +Pueblo came out of the client of the same name in the mid-nineties and took a direct approach to +enhancing MUD text: let the server send **HTML**, and let the client render it. A server announces +Pueblo support in a line at connect; the client replies, and from then on the stream may carry +markup. + +It reached the MUSH side of the hobby more than the MUD side, and MUSH servers that support it +generally still do. + +## Not MXP + +[MXP](/reference/protocols/mxp) is the later scheme and the more widely implemented one. They do a +similar job and are not compatible, and reading a client's Pueblo support as MXP support — or the +reverse — is the single easiest mistake to make when compiling a client comparison. The client pages +in this section keep them separate for that reason, and where a project documents one and not the +other, the other says *unknown*. + +## What we measure + +Pueblo's handshake is not a telnet option in the usual sense, so what we observe is narrower than +for the negotiated protocols, and a low figure here should be read as a statement about our +visibility rather than about deployment. diff --git a/content/reference/protocol-tls.md b/content/reference/protocol-tls.md new file mode 100644 index 0000000..a0696b1 --- /dev/null +++ b/content/reference/protocol-tls.md @@ -0,0 +1,37 @@ +--- +kind: protocol +slug: tls +title: TLS +summary: Encrypted connections. Usually a separate port rather than a negotiated upgrade, and the one capability on this site we verify by connecting rather than by asking. +protocol: TLS +see-also: connecting +see-also: protocols/charset +see-also: clients/potato +--- + +Telnet is plaintext. Everything you send a MU\* — including your password — crosses the network +readable by anything on the path, unless the game offers TLS. + +In this hobby TLS almost always means **a second port that speaks TLS from the first byte**, not an +in-band upgrade. A game with a plain port on 4201 and a TLS port on 4202 is the common shape. There +is a negotiated variant, and it is rare enough that at least one client's documentation explicitly +says it is not supported. + +## Why the game pages mark this specially + +TLS is the one capability on this site established by *doing it*: an endpoint is marked TLS because +we completed a TLS handshake against it. There is no asking involved and no field to declare, which +makes it the cleanest measurement in the catalogue. + +That is also why a game's TLS port and its plain port are listed as separate endpoints rather than +merged. They are different measurements of different things. + +## Practical advice + +If a game you play offers a TLS port, use it. If it does not and you care, ask — it is a small +amount of work for an administrator, and the reason it is not universal is mostly that nobody has +asked rather than that anybody objects. + +Check whether your client supports it before you rely on it. Several in the [clients](/reference) +section do; at least one documents a workaround with an external `stunnel` process instead, which +works and is more setup than most people will do. diff --git a/content/reference/protocol-ttype.md b/content/reference/protocol-ttype.md new file mode 100644 index 0000000..31106d5 --- /dev/null +++ b/content/reference/protocol-ttype.md @@ -0,0 +1,44 @@ +--- +kind: protocol +slug: ttype +title: TTYPE and MTTS +summary: How a client tells a server what it is and what it can do — including, if the client chooses to say so, that a screen reader is in use. +protocol: TTYPE +home: https://www.mudhalla.net/tintin/protocols/mtts/ +see-also: protocols/charset +see-also: clients/tintin +see-also: clients/blightmud +--- + +TTYPE is telnet option 24, from RFC 1091: the server asks the client what terminal it is, and the +client answers. Historically the answer was `VT100` or `ANSI`. + +**MTTS** — the Mud Terminal Type Standard — layers a convention on top. A client answers three times: +its name, its terminal type, and then `MTTS `, where the bits declare capabilities. 256 +colours, true colour, UTF-8, MNES, MSP over out-of-band — and, notably, **`MTTS_SCREEN_READER`**. + +## The screen-reader bit + +That last one is worth pausing on, because it is the only place in this hobby's protocol stack where +accessibility is a first-class concept. + +A client that sets it is telling the server that a screen reader is in use, and a server that +notices can adapt: suppress ASCII art, drop the decorative box-drawing around a room description, +change how a table is laid out. Both [TinTin++](/reference/clients/tintin) and +[Blightmud](/reference/clients/blightmud) advertise it, and [Mudlet](/reference/clients/mudlet) has a +setting for it. + +Whether any given game acts on it is a different question, and not one this site can measure — we +cannot ask a server what it would do differently. + +## What a crawler owes here + +A crawler identifies itself through TTYPE, and it should. Ours does, with an information URL, so an +administrator reading their logs can find out who has been connecting to their game and how to ask +us to stop. A crawler that answers `ANSI` and nothing else is anonymous by design, and there is no +good reason for that. + +## What we measure + +Servers that negotiated TTYPE with us. Note that this is one of the few options where *we* are the +side being asked, so a figure here is a count of servers that cared to ask. diff --git a/src/MUI.Catalog/CodebaseFamily.cs b/src/MUI.Catalog/CodebaseFamily.cs new file mode 100644 index 0000000..aaedd1a --- /dev/null +++ b/src/MUI.Catalog/CodebaseFamily.cs @@ -0,0 +1,90 @@ +namespace MUI.Catalog; + +/// +/// Matching a game's identified codebase against the family a reference page is about. +/// +/// +/// +/// A game's CODEBASE carries a version — PennMUSH 1.8.8p0, TinyMUX 2.12 — and +/// the question a reader asks is never version-shaped. "How many games run PennMUSH" has to gather +/// every patchlevel, so the facet is a family name matched as a prefix rather than an equality. +/// +/// +/// The prefix is bounded on the right, which is not fussiness: MOO against LambdaMOO +/// is already handled by anchoring at the start, but ROM against ROMulus is not, and +/// a facet that silently absorbs a neighbouring family produces a count that is wrong in the +/// direction nobody checks. A match therefore ends at the string's end or at a character that is +/// not a letter or a digit. +/// +/// +/// It lives here rather than in the web tier because two implementations of +/// and the reference pages all have to agree about it, and the count a +/// page prints has to be the count the listing behind its link contains. +/// +/// +/// Two methods because two surfaces need this, and they must agree. The ecosystem +/// dashboard groups by and a reference page filters by +/// ; if those were separate implementations, a codebase page's own +/// count and the listing it links to would be free to disagree about what a family is. +/// +/// +public static class CodebaseFamily +{ + public static string Of(string codebase) + { + ArgumentNullException.ThrowIfNull(codebase); + + var trimmed = codebase.Trim(); + var space = trimmed.LastIndexOf(' '); + + if (space <= 0) + { + return trimmed; + } + + return LooksLikeAVersion(trimmed[(space + 1)..]) + ? trimmed[..space].TrimEnd() + : trimmed; + } + + private static bool LooksLikeAVersion(string token) + { + if (token.Length == 0) + { + return false; + } + + var starts = char.IsAsciiDigit(token[0]) + || (token[0] is 'v' or 'V' && token.Length > 1 && char.IsAsciiDigit(token[1])); + + return starts && token.All(c => char.IsAsciiLetterOrDigit(c) || c is '.' or '-' or '_'); + } + + /// + /// Whether — a game's identified codebase, version and all — belongs + /// to the family named by . A game with no identified codebase belongs + /// to no family: not identifying it is a measurement, and it is not a measurement of this. + /// + public static bool Matches(string? codebase, string? family) + { + if (string.IsNullOrWhiteSpace(family)) + { + return true; + } + + if (string.IsNullOrWhiteSpace(codebase)) + { + return false; + } + + var name = codebase.AsSpan().Trim(); + var wanted = family.AsSpan().Trim(); + + if (!name.StartsWith(wanted, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + return name.Length == wanted.Length || !char.IsLetterOrDigit(name[wanted.Length]); + } +} diff --git a/src/MUI.Catalog/Ecosystem.cs b/src/MUI.Catalog/Ecosystem.cs index 82722f3..ee0c920 100644 --- a/src/MUI.Catalog/Ecosystem.cs +++ b/src/MUI.Catalog/Ecosystem.cs @@ -139,57 +139,6 @@ public static class EcosystemProtocols public static IReadOnlyList Headline { get; } = ["TLS", "UTF-8", "GMCP", "MXP"]; } -/// -/// The family a CODEBASE value names, with a trailing version folded away. -/// -/// -/// -/// Market share is a question about codebases and not about point releases: PennMUSH 1.8.8p0 and -/// PennMUSH 1.8.7 are one answer, and reporting them as two would spread one codebase's share across -/// as many rows as there are patch levels in the wild. MSSP's own convention is name-then-version, -/// which is what makes the fold possible at all. -/// -/// -/// Exactly one trailing token is folded, and only when the whole of it looks like a version — it -/// starts with a digit or a v before one, and contains nothing but letters, digits and the -/// separators a version number uses. So Midnight Sun keeps both its words and -/// Rhost 4.0.4 (patchlevel 1) keeps its parenthesis rather than being truncated mid-phrase. -/// The value as the game reported it is still on the game's own page; this is the dashboard's -/// grouping key and nothing else. -/// -/// -public static class CodebaseFamily -{ - public static string Of(string codebase) - { - ArgumentNullException.ThrowIfNull(codebase); - - var trimmed = codebase.Trim(); - var space = trimmed.LastIndexOf(' '); - - if (space <= 0) - { - return trimmed; - } - - return LooksLikeAVersion(trimmed[(space + 1)..]) - ? trimmed[..space].TrimEnd() - : trimmed; - } - - private static bool LooksLikeAVersion(string token) - { - if (token.Length == 0) - { - return false; - } - - var starts = char.IsAsciiDigit(token[0]) - || (token[0] is 'v' or 'V' && token.Length > 1 && char.IsAsciiDigit(token[1])); - - return starts && token.All(c => char.IsAsciiLetterOrDigit(c) || c is '.' or '-' or '_'); - } -} /// /// One game in the busiest ranking, with the measurements the rank is computed from beside it. diff --git a/src/MUI.Catalog/Facets.cs b/src/MUI.Catalog/Facets.cs index 5340de3..1c99726 100644 --- a/src/MUI.Catalog/Facets.cs +++ b/src/MUI.Catalog/Facets.cs @@ -31,6 +31,13 @@ public static class FacetKeys public const string Codebase = "codebase"; + /// + /// The codebase with its version taken off. Its own key, because is the + /// counted facet over raw values and the two answer different questions (see + /// GameFilter.CodebaseFamily). + /// + public const string CodebaseFamily = "codebase-family"; + public const string Family = "family"; public const string Genre = "genre"; @@ -243,8 +250,14 @@ public static GameListing Search(IReadOnlyList rows, GameFilter fi // database and the demo fixture disagreed about that until this became one function. var wantsArchived = filter.IncludeArchived || filter.Band is ActivityBand.Archived; + // The codebase family narrows the base set rather than being offered as a counted facet, + // which is deliberate: a reference page links here to say "the games running PennMUSH", and + // the facet counts on the page it lands on should be counts *within* that codebase. It sits + // beside the text search for the same reason — both are the question, not an answer to it. var baseRows = rows - .Where(r => (wantsArchived || r.Band is not ActivityBand.Archived) && MatchesText(r, filter.Text)) + .Where(r => (wantsArchived || r.Band is not ActivityBand.Archived) + && MatchesText(r, filter.Text) + && CodebaseFamily.Matches(r.Codebase, filter.CodebaseFamily)) .ToList(); var results = baseRows.Where(r => Chosen(r, filter, null) && Present(r, filter)).ToList(); diff --git a/src/MUI.Catalog/Views.cs b/src/MUI.Catalog/Views.cs index 148ea17..2018d7b 100644 --- a/src/MUI.Catalog/Views.cs +++ b/src/MUI.Catalog/Views.cs @@ -159,6 +159,10 @@ public sealed record GameFilter public FacetChoice? Charset { get; init; } + /// + /// The codebase exactly as a game reports it, PennMUSH 1.8.8p0 and all — a counted facet + /// over the values actually present in the catalogue. + /// public FacetChoice? Codebase { get; init; } public FacetChoice? Family { get; init; } @@ -166,6 +170,26 @@ public sealed record GameFilter public FacetChoice? Genre { get; init; } public FacetChoice? Language { get; init; } + + /// + /// A codebase familyPennMUSH, not PennMUSH 1.8.8p0. Matched by + /// , so every patchlevel of one codebase is one facet. + /// + /// + /// + /// This exists because a reference page for a codebase has to link to the games running it, and + /// a link is only honest if the page's own count and the listing it lands on are one query. Free + /// text will not do the job: ?q=PennMUSH searches names against the database and would + /// find the games called PennMUSH rather than the games running it. + /// + /// + /// Distinct from and from , and all three are real. + /// Codebase is the raw string a game published; Family is MSSP's own FAMILY + /// variable, which answers TinyMUD or DikuMUD; this is the codebase with its + /// version taken off. A reference page for PennMUSH wants the third and neither of the others. + /// + /// + public string? CodebaseFamily { get; init; } } /// diff --git a/src/MUI.Web/Api/ApiModels.cs b/src/MUI.Web/Api/ApiModels.cs index 2bb5755..1def9cf 100644 --- a/src/MUI.Web/Api/ApiModels.cs +++ b/src/MUI.Web/Api/ApiModels.cs @@ -194,7 +194,8 @@ public sealed record FilterView( string? Codebase, string? Family, string? Genre, - string? Language) + string? Language, + string? CodebaseFamily) { public static FilterView Of(GameFilter filter) { @@ -211,7 +212,8 @@ public static FilterView Of(GameFilter filter) filter.Codebase?.Token, filter.Family?.Token, filter.Genre?.Token, - filter.Language?.Token); + filter.Language?.Token, + filter.CodebaseFamily); } } diff --git a/src/MUI.Web/Api/GameFilterBinding.cs b/src/MUI.Web/Api/GameFilterBinding.cs index dd0ae51..44d9545 100644 --- a/src/MUI.Web/Api/GameFilterBinding.cs +++ b/src/MUI.Web/Api/GameFilterBinding.cs @@ -71,6 +71,11 @@ private static bool TryRead( var protocols = Protocols(read); var text = read(FacetKeys.Text).ToString(); + // A codebase *family*, matched as a prefix — PennMUSH, not PennMUSH 1.8.8p0. It has its own + // key because `codebase` is taken by the counted facet over raw values, and + // an API that invented a second name for the same question would let the two drift. + var codebaseFamily = read(FacetKeys.CodebaseFamily).ToString(); + var filter = new GameFilter { Text = string.IsNullOrWhiteSpace(text) ? null : text, @@ -84,6 +89,7 @@ private static bool TryRead( Family = Choice(read, FacetKeys.Family), Genre = Choice(read, FacetKeys.Genre), Language = Choice(read, FacetKeys.Language), + CodebaseFamily = string.IsNullOrWhiteSpace(codebaseFamily) ? null : codebaseFamily.Trim(), }; result = new GameQuery( diff --git a/src/MUI.Web/Components/ClientCapabilityMatrix.razor b/src/MUI.Web/Components/ClientCapabilityMatrix.razor new file mode 100644 index 0000000..4b7a045 --- /dev/null +++ b/src/MUI.Web/Components/ClientCapabilityMatrix.razor @@ -0,0 +1,84 @@ +@using MUI.Web.Reference + +@* + A client's capability matrix: one hand-written column, and the source that backs each cell. + + This is deliberately shaped differently from the game pages' CapabilityMatrix, which has two + columns because it has two kinds of claim to hold apart. Here there is only one kind — somebody + read somebody else's documentation — so a second column would imply a measurement that does not + exist. What takes its place is the citation, which is the load-bearing part of the record: an + uncited cell is not a weaker claim, it is not a claim at all, and it renders as unknown. +*@ + +
+

Capabilities

+

@Established of @Claims.Count established from the project's own documentation

+
+ +

@ReferencePlainText.ClientMatrixCaveat

+ + + + + + + + + + + + @foreach (var claim in Claims) + { + + + + @* The word is the state and the colour only reinforces it. In greyscale, in a screen + reader and on a printout the cell still says which of the three things it is — + which is the entire difference between an unknown and a no. *@ + + + + } + +
+ Client capabilities, each read off the project's own documentation. Unknown means we did not + establish it, and never that the client lacks it. +
capabilitydocumentedsource
@claim.Name + @ClientCapabilities.Word(claim.State) + + @if (claim.Source is { } source) + { + @Host(source) + } + else + { + we did not find one + } +
+ +@code { + [Parameter, EditorRequired] public IReadOnlyList Claims { get; set; } = []; + + [Parameter] public string Id { get; set; } = "capabilities"; + + private int Established => Claims.Count(c => c.IsClaimed); + + /// + /// Accent for a documented yes, amber for a documented no, and the unknown tone for the rest — + /// the site's one colour rule (measured / declared / absent) applied to a table where every cell + /// is on the declared side of it. + /// + private static string Tone(CapabilityState state) => state switch + { + CapabilityState.Present => "state-present", + CapabilityState.Absent => "state-absent", + _ => "state-unknown", + }; + + /// + /// The host, not the whole URL. A citation column of eighty-character links is unreadable, and + /// the link itself still goes where it says. + /// + private static string Host(string source) => + Uri.TryCreate(source, UriKind.Absolute, out var uri) ? uri.Host : source; +} diff --git a/src/MUI.Web/Components/Layout/MainLayout.razor b/src/MUI.Web/Components/Layout/MainLayout.razor index 91e6554..b0ce15a 100644 --- a/src/MUI.Web/Components/Layout/MainLayout.razor +++ b/src/MUI.Web/Components/Layout/MainLayout.razor @@ -25,6 +25,7 @@
games archive random + reference ecosystem rankings about diff --git a/src/MUI.Web/Components/Pages/Games.razor b/src/MUI.Web/Components/Pages/Games.razor index 56e3e02..9689e31 100644 --- a/src/MUI.Web/Components/Pages/Games.razor +++ b/src/MUI.Web/Components/Pages/Games.razor @@ -30,6 +30,18 @@ else { + @if (Filter.CodebaseFamily is { } family) + { + @* The filter a reference page links in on. It is a filter and not a search, so it says + which family it is showing and offers the way back out — a reader who arrived from + /reference/codebases/pennmush should not have to guess why the listing is short. *@ +

+ codebase @family · + what this codebase is · + every game +

+ } +

@Listing.Games.Count games · counts measured, never asserted · show me a random one diff --git a/src/MUI.Web/Components/Pages/ReferenceEntry.razor b/src/MUI.Web/Components/Pages/ReferenceEntry.razor new file mode 100644 index 0000000..8c23aad --- /dev/null +++ b/src/MUI.Web/Components/Pages/ReferenceEntry.razor @@ -0,0 +1,205 @@ +@page "/reference/{Slug}" +@page "/reference/codebases/{Slug}" +@page "/reference/clients/{Slug}" +@page "/reference/protocols/{Slug}" +@using MUI.Web.Reference +@inject IGameQueries Queries +@inject NavigationManager Navigation + +@* + One reference page, whichever kind it is. + + The four routes resolve through the document's own Path rather than through a kind parameter, so + a page's URL has exactly one spelling — the one every link on the site is built from. A kind + passed separately would let a link and a route disagree, and the failure would be a page that + renders the wrong measurement beside the right prose. + + The prose is ours and the figures are measured, and that split is the reason the section exists. + Nothing below reads a number out of a content file; every count is a query made on this request. +*@ + +@if (Document is null) +{ +

Not found

+

+ No reference page here. This section is hand-written, so a gap is work nobody has done rather + than something that was removed — see what there is. +

+} +else if (Plain) +{ +
@ReferencePlainText.Render(Document, Codebase, Protocol, Related)
+} +else +{ + @Document.Title — mu*index + +
+
+

@ReferencePlainText.Kind(Document.Kind)

+

@Document.Title

+

@Document.Summary

+

+ @if (Document.Home is { } home) + { + @home + } + @if (Document.Platforms.Count > 0) + { + · @string.Join(", ", Document.Platforms) + } +

+
+ + @if (Codebase is { } codebase) + { +
+

Games running it

+ @if (codebase.Known == 0) + { + @* Never a zero standing on its own. Zero here is a statement about our coverage + and reads as a statement about the codebase unless it is spelled out. *@ +

+ We have not identified any yet. That is a fact about what this crawler has + measured, not about what exists — a game we have not reached, or whose + codebase we could not read, is not counted here. +

+ } + else + { +

+ @codebase.Listed listed + · @codebase.Archived archived · measured, never asserted +

+

+ Counted from the catalogue on this request, over the same filter the link + above carries — so this number and that listing are one query and cannot + drift apart. +

+ @if (codebase.MeasuredProtocols.Count > 0) + { +

+ offered in their handshakes: + @string.Join(", ", codebase.MeasuredProtocols) +

+ } + } +
+ } + + @if (Protocol is { } protocol) + { +
+

Measured adoption

+ @if (protocol.Listed == 0) + { +

Nothing measured yet.

+ } + else + { +

+ @protocol.Offering + + of @protocol.Listed listed games — @Wording.Percent(protocol.Share ?? 0) + +

+

@ReferencePlainText.ProtocolRemainderCaveat

+ + @if (MeasuredRows.Count > 0) + { + + + + + + + + + + + @foreach (var row in MeasuredRows) + { + + + + + + } + +
+ Games observed offering @Document.Title in a handshake, by the + codebase we identified them as running. +
codebaseoffered itidentified
@row.Codebase + @row.Offering + @row.Identified
+ } + } +
+ } + + @if (Document.Kind is ReferenceKind.Client) + { + + } + +
@((MarkupString)Html)
+ + @if (Related.Count > 0) + { +

See also

+
    + @foreach (var other in Related) + { +
  • + @other.Title + @other.Summary +
  • + } +
+ } + +

read this page as plain text

+
+} + +@code { + [Parameter] public string Slug { get; set; } = string.Empty; + + [SupplyParameterFromQuery(Name = "plain")] private string? PlainFlag { get; set; } + + private bool Plain => Truthy.Is(PlainFlag); + + private ReferenceDocument? Document; + private CodebaseFigures? Codebase; + private ProtocolFigures? Protocol; + private IReadOnlyList Related = []; + private IReadOnlyList Claims = []; + private string Html = string.Empty; + + private IReadOnlyList MeasuredRows => + [.. (Protocol?.ByCodebase ?? []).Where(r => r.IsMeasured)]; + + protected override async Task OnParametersSetAsync() + { + var library = ReferenceLibrary.Shipped; + + Document = library.Find(new Uri(Navigation.Uri).LocalPath.TrimEnd('/')); + + if (Document is null) + { + return; + } + + Related = library.Related(Document); + Claims = ClientCapabilities.For(Document); + Html = ReferenceMarkdown.ToHtml(Document.Body); + + Codebase = Document.Codebase is { } family + ? await CodebaseFigures.ReadAsync(Queries, family) + : null; + + Protocol = Document.Protocol is { } protocol + ? await ProtocolFigures.ReadAsync(Queries, protocol, library.OfKind(ReferenceKind.Codebase)) + : null; + } +} diff --git a/src/MUI.Web/Components/Pages/ReferenceIndex.razor b/src/MUI.Web/Components/Pages/ReferenceIndex.razor new file mode 100644 index 0000000..beccbe5 --- /dev/null +++ b/src/MUI.Web/Components/Pages/ReferenceIndex.razor @@ -0,0 +1,66 @@ +@page "/reference" +@using MUI.Web.Reference + +@* + The reference section's front door. + + Spec §9's argument for this whole section is that it obtains wiki value without wiki governance: + the content is curated, single-author and versioned in git. That has to be said on the page — + a reader who assumes they are looking at a wiki will read a gap as vandalism rather than as work + nobody has done yet, and will look for an edit button that will never exist. +*@ + +@if (Plain) +{ +
@ReferencePlainText.RenderIndex(Library)
+} +else +{ + Reference — mu*index + +

Reference

+

+ What the codebases are, what the clients do, and what the protocols mean. Written by hand and + kept in the repository beside the crawler — not a wiki, and there is nothing on this page to + edit. Every number here is a different thing: it comes from the catalogue and is + recomputed each time you load the page. +

+ + @foreach (var kind in Kinds) + { + var documents = Library.OfKind(kind); + + if (documents.Count == 0) + { + continue; + } + +
+

@ReferencePlainText.Heading(kind)

+
    + @foreach (var document in documents) + { +
  • + @document.Title + @document.Summary +
  • + } +
+
+ } + +

read this page as plain text

+} + +@code { + [SupplyParameterFromQuery(Name = "plain")] private string? PlainFlag { get; set; } + + private bool Plain => Truthy.Is(PlainFlag); + + private static readonly ReferenceKind[] Kinds = + [ + ReferenceKind.Orientation, ReferenceKind.Codebase, ReferenceKind.Client, ReferenceKind.Protocol, + ]; + + private static ReferenceLibrary Library => ReferenceLibrary.Shipped; +} diff --git a/src/MUI.Web/Components/PlainText.cs b/src/MUI.Web/Components/PlainText.cs index c18345d..ffc40e5 100644 --- a/src/MUI.Web/Components/PlainText.cs +++ b/src/MUI.Web/Components/PlainText.cs @@ -618,7 +618,7 @@ private static void Heading(StringBuilder b, string title) } /// Wraps to 80 columns, because that is the width a text browser has. - private static void Wrap(StringBuilder b, string text, string indent = "") + internal static void Wrap(StringBuilder b, string text, string indent = "") { var width = Columns - indent.Length; var line = new StringBuilder(); diff --git a/src/MUI.Web/Components/ReferencePlainText.cs b/src/MUI.Web/Components/ReferencePlainText.cs new file mode 100644 index 0000000..d0669b7 --- /dev/null +++ b/src/MUI.Web/Components/ReferencePlainText.cs @@ -0,0 +1,253 @@ +using System.Text; + +using MUI.Catalog; +using MUI.Web.Reference; + +namespace MUI.Web.Components; + +/// +/// The reference section in plain text. +/// +/// +/// +/// Same rule as the rest of the plain surface: if a fact cannot survive here, its graphic on the +/// main page is decoration. For this section that bites hardest on the matrices — a client +/// capability table carried by a tick, a cross and a blank does not survive, and one carried by +/// yes, no and unknown does. +/// +/// +/// The prose goes through the Markdown renderer's text output and the structured half is rendered +/// here from the same records the graphical page uses, so the two surfaces cannot report different +/// counts. Nothing here re-reads the content files. +/// +/// +public static class ReferencePlainText +{ + /// + /// The one sentence that keeps a client matrix honest, on both surfaces. + /// + /// + /// A client cannot be probed — there is no handshake of ours to observe — so every cell is a + /// reading of somebody's documentation. Saying so beside the table is not modesty: this site's + /// whole claim is that a reader can tell a measurement from an assertion, and a table that looks + /// like the game pages' measured matrix while being neither would spend that credit. + /// + public const string ClientMatrixCaveat = + "Read off each project's own documentation, not measured by us — a client has no handshake " + + "for us to observe. \"unknown\" means we looked and did not establish it. It never means no."; + + /// + /// The sentence a protocol page's remainder needs, on both surfaces. + /// + public const string ProtocolRemainderCaveat = + "The games not counted here are not games without the protocol. A game is counted when we " + + "observed its server offering the option in a handshake; the rest are servers that did not " + + "offer it to us and servers whose handshake we have not read, and we cannot tell you which."; + + public static string Render( + ReferenceDocument document, + CodebaseFigures? codebase = null, + ProtocolFigures? protocol = null, + IReadOnlyList? related = null) + { + ArgumentNullException.ThrowIfNull(document); + + var b = new StringBuilder(); + + b.AppendLine($"{document.Title.ToUpperInvariant()} [{Kind(document.Kind)}]"); + PlainText.Wrap(b, document.Summary); + + if (document.Home is { } home) + { + b.AppendLine(home); + } + + if (document.Platforms.Count > 0) + { + b.AppendLine($"Runs on: {string.Join(", ", document.Platforms)}"); + } + + if (codebase is not null) + { + AppendCodebase(b, document, codebase); + } + + if (protocol is not null) + { + AppendProtocol(b, document, protocol); + } + + if (document.Kind is ReferenceKind.Client) + { + AppendClientMatrix(b, document); + } + + b.AppendLine(); + b.AppendLine(ReferenceMarkdown.ToPlainText(document.Body)); + + if (related is { Count: > 0 }) + { + b.AppendLine(); + b.AppendLine("See also"); + foreach (var other in related) + { + b.AppendLine($" {other.Title} — {other.Path}"); + } + } + + return b.ToString(); + } + + /// + /// The measured half of a codebase page. It says we identified rather than there + /// are, because a game whose codebase we could not read is not a game running something + /// else — and the difference is the whole reason the figure is worth printing. + /// + private static void AppendCodebase(StringBuilder b, ReferenceDocument document, CodebaseFigures figures) + { + b.AppendLine(); + b.AppendLine("Games we have identified as running this codebase"); + + if (figures.Known == 0) + { + PlainText.Wrap(b, "None yet. That is a statement about what we have measured, not about " + + "what exists — a game we have not reached, or whose codebase we could not read, is " + + "not counted here.", " "); + return; + } + + b.AppendLine($" {figures.Listed} listed, {figures.Archived} archived"); + + if (document.GamesPath is { } path) + { + b.AppendLine($" {path}"); + } + + b.AppendLine(figures.MeasuredProtocols.Count > 0 + ? $" Measured in their handshakes: {string.Join(", ", figures.MeasuredProtocols)}" + : " Nothing was offered in any handshake we have read from them."); + } + + private static void AppendProtocol(StringBuilder b, ReferenceDocument document, ProtocolFigures figures) + { + b.AppendLine(); + b.AppendLine("Measured adoption"); + + if (figures.Listed == 0) + { + b.AppendLine(" Nothing measured yet."); + return; + } + + b.AppendLine($" {figures.Offering} of {figures.Listed} listed games were observed offering it " + + $"({Wording.Percent(figures.Share ?? 0)})"); + + if (document.GamesPath is { } path) + { + b.AppendLine($" {path}"); + } + + b.AppendLine(); + PlainText.Wrap(b, ProtocolRemainderCaveat, " "); + + var rows = figures.ByCodebase.Where(r => r.IsMeasured).ToList(); + + if (rows.Count == 0) + { + return; + } + + b.AppendLine(); + b.AppendLine(" By codebase, of the games we identified"); + foreach (var row in rows) + { + b.AppendLine($" {row.Codebase,-20} {row.Offering,4} of {row.Identified,-4} offered it"); + } + } + + private static void AppendClientMatrix(StringBuilder b, ReferenceDocument document) + { + b.AppendLine(); + b.AppendLine("Capabilities"); + PlainText.Wrap(b, ClientMatrixCaveat, " "); + b.AppendLine(); + + var claims = ClientCapabilities.For(document); + + foreach (var claim in claims) + { + b.AppendLine($" {claim.Name,-16} {ClientCapabilities.Word(claim.State)}"); + + // The source on its own line, unwrapped. A URL broken across two lines is not a URL, and + // this is the one place the eighty-column rule gives way to a thing being usable. + if (claim.Source is { } source) + { + b.AppendLine($" {source}"); + } + } + + var unknown = claims.Count(c => c.State is CapabilityState.Unknown); + + if (unknown > 0) + { + b.AppendLine(); + PlainText.Wrap(b, $"{unknown} of {claims.Count} rows are unknown: we did not find the " + + "project's own documentation saying either way. A short honest table beats a long " + + "guessed one.", " "); + } + } + + /// The index, which is the only page in the section that is a list of the others. + public static string RenderIndex(ReferenceLibrary library) + { + ArgumentNullException.ThrowIfNull(library); + + var b = new StringBuilder(); + + b.AppendLine("REFERENCE"); + PlainText.Wrap(b, "Hand-written, single-author, and versioned in git. The prose here is ours; " + + "every number beside it was measured by the crawler and is recomputed on each request. " + + "This is not a wiki, and there is no way to edit it from this page."); + + foreach (var kind in new[] + { + ReferenceKind.Orientation, ReferenceKind.Codebase, ReferenceKind.Client, ReferenceKind.Protocol, + }) + { + var documents = library.OfKind(kind); + + if (documents.Count == 0) + { + continue; + } + + b.AppendLine(); + b.AppendLine(Heading(kind).ToUpperInvariant()); + + foreach (var document in documents) + { + b.AppendLine($" {document.Title}"); + b.AppendLine($" {document.Path}"); + PlainText.Wrap(b, document.Summary, " "); + } + } + + return b.ToString(); + } + + public static string Kind(ReferenceKind kind) => kind switch + { + ReferenceKind.Codebase => "codebase", + ReferenceKind.Client => "client", + ReferenceKind.Protocol => "protocol", + _ => "orientation", + }; + + public static string Heading(ReferenceKind kind) => kind switch + { + ReferenceKind.Codebase => "Codebases", + ReferenceKind.Client => "Clients", + ReferenceKind.Protocol => "Protocols", + _ => "Start here", + }; +} diff --git a/src/MUI.Web/MUI.Web.csproj b/src/MUI.Web/MUI.Web.csproj index 4e5b009..1e058ab 100644 --- a/src/MUI.Web/MUI.Web.csproj +++ b/src/MUI.Web/MUI.Web.csproj @@ -11,6 +11,26 @@ + + + + + + + + +