Skip to content

Ask GMCP/MSDP games for data and show it - #70

Merged
HarryCordewener merged 1 commit into
claude/project-thread-hgawpe-1afrom
claude/project-thread-hgawpe-1b
Oct 26, 2026
Merged

HarryCordewener merged 1 commit into
claude/project-thread-hgawpe-1afrom
claude/project-thread-hgawpe-1b

Conversation

@HarryCordewener

Copy link
Copy Markdown
Member

Requested by Grave · project thread

Item 1b of the implementation plan. Stacked on 1a (#69).

What changed

  • Core.Hello and Core.Supports.Set (Char, Char.Skills, Char.Items, Room, Comm.Channel) once GMCP is agreed; MSDP REPORTs for the variables the status row reads. F5 gmcp modules adds more per world.
  • GameDataStore per session; status-row meters (F7 ▸ meters); ⌃P ▸ Show game data; Lua gmcp.get, msdp.get, gmcp.send.

Decisions

  • Nothing GMCP is sent before the server agrees (the unsolicited-negotiation rule). MSDP has no negotiated callback in the library, so IAC DO MSDP is spotted on the write path and the reports follow it.
  • GMCP objects merge key by key (IRE and Aardwolf send partial Char.Vitals). MSDP variables are stored as MSDP.NAME, so one path syntax reads both.
  • Payloads over 64 KiB and packages past 512 are dropped.
  • A meter appears only once its value has arrived, and from then its width is reserved, so the row never reflows as numbers change.

Generated by Claude Code

@coderabbitai

coderabbitai Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Essentials
  • Run ID: cb451422-6a05-4461-8dc6-52af1a6e8777

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@HarryCordewener HarryCordewener self-assigned this Oct 26, 2026
@HarryCordewener
HarryCordewener added this pull request to stack #93 October 26, 2026 20:07
@HarryCordewener
HarryCordewener marked this pull request as ready for review October 26, 2026 20:08
The client now introduces itself over GMCP once the server agrees
(Core.Hello, Core.Supports.Set) and asks MSDP games to REPORT what the
status row reads. Game data is held per session in GameDataStore and
shown three ways: status-row meters (F7 meters), Ctrl+P > Show game
data, and Lua gmcp.get / msdp.get / gmcp.send. F5 gains a per-world
gmcp modules field. GmcpStats, which collected nine keys nothing drew,
is removed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WXyHEoxwX8GurTHxwPinQY
@HarryCordewener
HarryCordewener force-pushed the claude/project-thread-hgawpe-1b branch from fa6a2d8 to 80fc2e6 Compare October 26, 2026 20:25
@HarryCordewener
HarryCordewener merged commit fc8da74 into claude/project-thread-hgawpe-1a Oct 26, 2026
1 check passed
HarryCordewener added a commit that referenced this pull request Oct 26, 2026
…s name (#69)

* Run Lua: load each session's script files and call the functions rules name

Every rule kind with a script field (trigger, alias, timer, macro) now raises
WorldSession.ScriptRequested with the match and its groups. SessionScripts
builds a ScriptHost per session from the trigger sets' ScriptFiles, read from
<config>/scripts only, and rebuilds it when the file list changes or on
Ctrl+P > Reload scripts. Errors go to the session's window and the client
messages. F5 gains a scripts field on each trigger set.

Lua runs on the thread that raised the request, under the host's lock.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WXyHEoxwX8GurTHxwPinQY

* Run script-only macros from a key press; count loaded scripts

DispatchMacro required a command, so a macro naming only a Lua function
never reached HandleKeyAsync and the key fell through to the command
line. The reload notice counted the files named rather than the files
that loaded, so a missing file was followed by "1 script loaded".

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WXyHEoxwX8GurTHxwPinQY

* Ask GMCP/MSDP games for data and show it (#70)

The client now introduces itself over GMCP once the server agrees
(Core.Hello, Core.Supports.Set) and asks MSDP games to REPORT what the
status row reads. Game data is held per session in GameDataStore and
shown three ways: status-row meters (F7 meters), Ctrl+P > Show game
data, and Lua gmcp.get / msdp.get / gmcp.send. F5 gains a per-world
gmcp modules field. GmcpStats, which collected nine keys nothing drew,
is removed.


Claude-Session: https://claude.ai/code/session_01WXyHEoxwX8GurTHxwPinQY

Co-authored-by: Claude <noreply@anthropic.com>

* Put content format, emoji and on-disconnect on F5 (#71)

Three settings existed only in config.json. F5's world row gains
content (auto/ansi/mxp/pueblo) and emoji (off/emoticons/shortcodes/
both); the character form gains disconnect under on connect.

ContentFormat gains AnsiOnly, appended so a stored "Ansi" keeps meaning
the automatic default. OnDisconnect was saved and never sent; it now
goes out before the client closes a connection itself, bounded by a
one-second timeout. Emoji switches are re-read on each connect.


Claude-Session: https://claude.ai/code/session_01WXyHEoxwX8GurTHxwPinQY

Co-authored-by: Claude <noreply@anthropic.com>

* Negotiate Pueblo and decode its ANSI colour (#72)

Register TelnetNegotiationCore's PuebloProtocol. A server's hello is
answered with PUEBLOCLIENT 2.50 when the world is auto or pueblo and
MXP is not already on; the server's start sequence switches an auto
world to PuebloParser. The parser swap is shared with MXP and only
happens off the plain ANSI parser, so the first markup negotiated
keeps the connection. Both handshake lines are cleared from the
pending prompt buffer.

PuebloParser gains the escape state machine MxpParser has, decoding
SGR through SgrCodes and consuming every other sequence.


Claude-Session: https://claude.ai/code/session_01WXyHEoxwX8GurTHxwPinQY

Co-authored-by: Claude <noreply@anthropic.com>

* Add the map model, store and path finder (#73)

Core/Mapping holds a world's rooms and areas (Mudlet's model: exits by
direction and by command, room and exit weights, locks), a per-endpoint
JSON store under maps/ that is memory-only without a directory, never
overwrites a newer client's file and moves an unreadable one aside,
and a Dijkstra path finder that returns the walk as commands.

Nothing uses it yet; room sources and the map window follow.


Claude-Session: https://claude.ai/code/session_01WXyHEoxwX8GurTHxwPinQY

Co-authored-by: Claude <noreply@anthropic.com>

* Record a map from GMCP rooms and text patterns (#74)

Room sources report where the character is: GmcpRoomSource reads
Room.Info in the IRE/Aardwolf shape (through TNC's typed RoomInfo) and
MUME's (object exits, Room.UpdateExits); TextRoomSource matches a
world's room-name and exits patterns. MapRecorder applies each report
on the UI thread, links it to the room walked from (WorldSession now
raises CommandSent for player commands, never the login line), places
rooms without server coordinates one step along the direction walked
with a nudge on collision, and recognises text rooms by the way back.

The app keeps one recorder per session and writes maps under maps/ on
disconnect and exit.


Claude-Session: https://claude.ai/code/session_01WXyHEoxwX8GurTHxwPinQY

Co-authored-by: Claude <noreply@anthropic.com>

* Add the map window, click-to-walk and speedwalk (#75)

Ctrl+P > Show map opens the focused connection's map as a tab. A click on
a room, or /walk <room>, walks there one step at a time; the walk stops off
the path, on the world's walk-fail pattern, after 8 s with no room, or on
disconnect. /map lock keeps walks out of a room. F5 gains the map room,
map exits and walk fail patterns, and Lua gets map.current/path/walk.


Claude-Session: https://claude.ai/code/session_01WXyHEoxwX8GurTHxwPinQY

Co-authored-by: Claude <noreply@anthropic.com>

* Add the media queue and cache for game sound (#76)

MCMP's Client.Media messages become MediaCommands, and a MediaQueue per
connection applies the specification's priority, key, continue and pause
rules to an IAudioOutput the host will supply. MediaCache fetches http(s)
files by a hash of their URL with name, extension and size limits, and
fetches nothing without a root.


Claude-Session: https://claude.ai/code/session_01WXyHEoxwX8GurTHxwPinQY

Co-authored-by: Claude <noreply@anthropic.com>

* Play game sound through a desktop audio device (#77)

SharpMUTerm.Audio opens the device with SoundFlow (miniaudio) on the first
MCMP command and decodes OGG with NVorbis. Each connection gets a media
queue; F7 has a SOUND section (on/off, three levels, captions), F5 a
per-world download switch, and Ctrl+P mutes for the run.


Claude-Session: https://claude.ai/code/session_01WXyHEoxwX8GurTHxwPinQY

Co-authored-by: Claude <noreply@anthropic.com>

* Read MSP sound triggers on connections that agreed MSP (#78)

A line that is exactly !!SOUND(...) or !!MUSIC(...) becomes a media
command with MSP's defaults, but only while the telnet session reports
option 90 agreed. Inert until TelnetNegotiationCore ships MSPProtocol.


Claude-Session: https://claude.ai/code/session_01WXyHEoxwX8GurTHxwPinQY

Co-authored-by: Claude <noreply@anthropic.com>

* Install packages of trigger sets, scripts and sounds (#79)

A package is a .zip read through a review that says what it adds and who it
applies to; nothing is written until Enter. Installed packages apply their
sets to characters on the games they name. F10 / Ctrl+P lists them, switches
one off, copies its sets into the player's own, and removes it.


Claude-Session: https://claude.ai/code/session_01WXyHEoxwX8GurTHxwPinQY

Co-authored-by: Claude <noreply@anthropic.com>

* Browse a package index and check its SHA-256 before the review (#80)

* Browse a package index and check its SHA-256 before the review

Ctrl+P > Browse packages fetches the index only when opened, lists the
focused game's packages first, and refuses an archive whose hash, id or
version is not the one the index names.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WXyHEoxwX8GurTHxwPinQY

* Read MXP definitions, entities, gauges, sounds and frames (#81)

* Offer every command of an MXP SEND menu

A <SEND> with several commands sends the first on a click and offers all of
them on a right-click: in the window's own menu, and in a terminal in a small
chooser. &text; in a command is filled in with the element's text.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WXyHEoxwX8GurTHxwPinQY

* Read MXP definitions, entities, gauges, sounds and frames

<!ELEMENT> and <!ENTITY> definitions, <VAR>, <GAUGE>/<STAT> meters on the
status row, <SOUND>/<MUSIC> through the MSP media path, and <FRAME>/<DEST>
spawn windows. All are secure tags; an element expands with the trust of
the tag that opened it, and every expansion is capped. Room flags feed the
automapper. A tag now ends only at a '>' outside a quoted value, and an
unfinished one is abandoned as text at the line boundary.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WXyHEoxwX8GurTHxwPinQY

---------

Co-authored-by: Claude <noreply@anthropic.com>

* Edit MOO verbs in the composer over MCP 2.1 (#82)

A MOO that speaks MCP opens @edit buffers in the F1 composer, and Ctrl+S
saves them back line for line (dns-org-mud-moo-simpleedit). The client
sends its name, version and window size (dns-com-vmoo-client) and shows
the MOO's status text (dns-com-awns-status). A prompt is now read from
the interpreter's own partial line, so a line the library consumes can
no longer be printed as the prompt after it.


Claude-Session: https://claude.ai/code/session_01WXyHEoxwX8GurTHxwPinQY

Co-authored-by: Claude <noreply@anthropic.com>

* Put a MUSH's edit grab on the command line (#83)

A world's edit grab prefix (F5, empty by default; AresMUSH uses
FugueEdit >) takes a matching line out of the output and puts its text
on that character's command line. Nothing is sent for the player, the
grab never lands on another character's bar, and a line being typed is
kept in history.


Claude-Session: https://claude.ai/code/session_01WXyHEoxwX8GurTHxwPinQY

Co-authored-by: Claude <noreply@anthropic.com>

* Complete the last word on the command line with Tab (#84)

Tab at the end of the command line completes the word being typed from
the game's recent output, with the names GMCP reports (room players,
channel members and talkers) first. Tab again cycles through the
matches and back to what was typed. With no completion, Tab keeps its
old meaning and moves to the second command line.


Claude-Session: https://claude.ai/code/session_01WXyHEoxwX8GurTHxwPinQY

Co-authored-by: Claude <noreply@anthropic.com>

* Let Lua scripts keep values between sessions (#85)

store.get, store.set and store.keys read and write one JSON file per
character in script-data/ beside config.json. The store is bounded at
the door (1,024 keys, 256 KB), written through a rename, survives a
script reload, and is memory-only in tests and snapshots.


Claude-Session: https://claude.ai/code/session_01WXyHEoxwX8GurTHxwPinQY

Co-authored-by: Claude <noreply@anthropic.com>

* Log in over GMCP Char.Login where a world opts in (#86)

A world with F5 ▸ gmcp login set to "if offered" asks for Char.Login 1
and holds back its connect line. A server that offers
password-credentials is sent the character's name and saved password
over GMCP. Any other answer, or none within five seconds, gets the
connect line as before. The password goes only to the wire.


Claude-Session: https://claude.ai/code/session_01WXyHEoxwX8GurTHxwPinQY

Co-authored-by: Claude <noreply@anthropic.com>

* Answer NEW-ENVIRON with the client's MNES identity (#87)

A server that asks over NEW-ENVIRON is told CLIENT_NAME, CLIENT_VERSION,
TERMINAL_TYPE and MTTS, taken from the TTYPE list so the two agree. The
MTTS flags now claim MNES. Nothing about the machine is sent, and
nothing is offered to a server that does not ask.


Claude-Session: https://claude.ai/code/session_01WXyHEoxwX8GurTHxwPinQY

Co-authored-by: Claude <noreply@anthropic.com>

* Send one line to several characters (#88)

Two or more open characters can be put in a send group from ⌃P; the
rail marks members with a diamond in the dot's cell. "Send the command
line to the group" sends the typed line once to each connected member
and names any that are not connected. Lua gains world.sendTo(name, cmd).


Claude-Session: https://claude.ai/code/session_01WXyHEoxwX8GurTHxwPinQY

Co-authored-by: Claude <noreply@anthropic.com>

* Show a game's Discord status when its world opts in (#89)

A world with F5 ▸ discord set to "show status" asks for
External.Discord 1, and the game's Info and Status go to the Discord
app on this computer over its local IPC while that world's window is
in front. Switching away, disconnecting or a world that has not opted
in clears it. The host supplies the presence; snapshots and tests have
none.


Claude-Session: https://claude.ai/code/session_01WXyHEoxwX8GurTHxwPinQY

Co-authored-by: Claude <noreply@anthropic.com>

* Support screen readers with --plain and the window's live regions (#90)

The client announces each line of output, its own messages and the
window brought forward to the host's announcer. sharpmuterm --plain
runs the same client as plain text on stdout and sends each line read
from stdin. The desktop window passes announcements to the screen
reader through live regions. A busy window can be kept quiet from ⌃P.


Claude-Session: https://claude.ai/code/session_01WXyHEoxwX8GurTHxwPinQY

Co-authored-by: Claude <noreply@anthropic.com>

* Check spelling in the F1 composer (#91)

Hunspell dictionaries through WeCantSpell.Hunspell, found beside config.json or
in the system folders and chosen by the POSIX locale. Unknown words are painted
in the warning colour through the editor's syntax-highlighting hook, and Alt+W
adds the word at the caret to the player's own list. The header names the
dictionary, or says there is none.


Claude-Session: https://claude.ai/code/session_01WXyHEoxwX8GurTHxwPinQY

Co-authored-by: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants