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 @@ + + + + + + + + +