The XMSG chat system - the C# library¶
THIS IS NOT THE SINTRAN CHAT PRODUCT. This page describes the C# chat library and its terminal door - a Windows-side implementation with its own commands (
chat joinat a#prompt) and its own API (ronny.Poll()).The product that runs on the ND-100s is a different program: the PLANC server
CHATSERand the PLANC clientCHAT, whose commands are/join,/who,/nickand so on. For that, read CHAT-APP-SPECIFICATION.md.Confusing the two is easy and this page caused it. The
tellcommand mentioned below exists HERE and has no equivalent on the SINTRAN server.
A chat room that lives on an XMSG node. Two kinds of people can be in the same room at the same time: somebody at a SINTRAN terminal who has connected to our node, and a program that talks to the room port to port.
1. The quickest thing that works: a terminal user¶
Somebody on a SINTRAN machine connects to our node the ordinary way (CONNECT-TO, then log in at
our TAD prompt). At the # prompt they have these commands:
| Command | What it does |
|---|---|
chat join <name> |
Enter the room under a name. With no name, your login name is used. |
chat say <text> |
Say something. Everybody in the room sees it as <name> text. |
chat who |
List who is in the room. |
chat nick <name> |
Change the name you are known by. Everybody is told, with the old name. |
chat part |
Leave the room. |
help lists them alongside the other terminal commands.
A worked example, two people on two terminals:
# chat join RONNY
you are in the room as RONNY
# chat join ANNA
you are in the room as ANNA
ANNA joined
# chat say god morgen
<ANNA> god morgen
<ANNA> god morgen
# chat nick RH
you are now RH
RONNY is now RH
The line you type is confirmed to you directly; everybody else is told separately. That is why
both sides see <ANNA> god morgen - the speaker included.
2. A program in the room¶
Xmsg.Chat holds the port-to-port half: ChatServer claims a name and admits joiners,
ChatClient joins and talks.
XmsgKernel kernel = new XmsgKernel(ourNode, ourMagic, null);
XroutDirectory directory = new XroutDirectory();
ChatServer server = new ChatServer(kernel, directory);
server.Open(ChatRooms.NameFor("LOBBY"), seats: 8, greeting: "welcome to the lobby");
ChatClient ronny = new ChatClient(kernel, directory, "RONNY");
ronny.Join(ChatRooms.NameFor("LOBBY"));
server.Poll(); // the server takes the join and answers
IReadOnlyList<ChatMessage> mine = ronny.Poll(); // Welcome, then Joined/Said/Renamed/Left
ronny.Say("god morgen");
ronny.Rename("RH");
ronny.Leave();
Poll on both sides is deliberate: nothing blocks, because the same loop tick also serves
everything else the node is doing.
The message kinds¶
| Kind | Direction | Carries |
|---|---|---|
Join / Welcome / Reject |
client -> server -> client | the name, and a reason when refused |
Say -> Said |
client -> room | the speaker's name and the text |
Rename -> Renamed |
client -> room | the NEW name, and the OLD one in the text |
Leave -> Left / Joined |
client -> room | who came or went |
Renamed carries both names on purpose: everybody has a transcript on screen under the old one
and no way to connect the two without it.
3. Rooms (channels)¶
A room is a name in the XROUT table with a CHAT- prefix, so rooms can be told apart from every
other server on the node:
ChatRooms.NameFor("LOBBY") // -> "CHAT-LOBBY", what the server registers
IReadOnlyList<XroutNameEntry> rooms = ChatRooms.List(directory);
// each entry: the SHORT name ("LOBBY") and the free seats XROUT still has
The free-seat count comes from XROUT, not from the room. That is the same number that decides whether a join is forwarded at all, so it is the true answer rather than what a room believes about itself.
Room listing does not cross the wire. No capture in the corpus shows a name-listing exchange between machines, so how one would look is unknown and has not been invented. A remote SINTRAN user cannot ask us for the list of rooms today.
4. Seats, and where they are enforced¶
A room has a size, and on the port-to-port path SINTRAN enforces it before any of our code runs: XROUT keeps a free-connection count, forwards a join only while it is above zero, and decrements it each time. So "the room is full" is decided by the system, and the real file server builds its 30 seats exactly this way.
The terminal path has no seat limit, because a terminal user does not come through XROUT. That is a real difference, not an oversight.
Two related rules worth knowing:
- Every refused JOIN returns a seat, because XROUT already took one to forward it. Forget that and the room fills up permanently while appearing empty.
- A refused RENAME returns nothing, because the member is still sitting in their seat. Handing one back there would let the room admit one person more than it holds.
5. What is shared, and why¶
The rules - who is in the room, which names are free, who is told what - live in one class,
ChatRoom, with no transport of any kind. Both doors carry them:
terminal user -> TadServer -> ChatRoom <- ChatServer <- port-to-port client
A member is an opaque number: a magic number on one path, a tty number on the other. Written twice, the two doors would drift, and the drift would be in the awkward cases - a duplicate nickname, a rename that collides, who hears about somebody leaving.
6. Status¶
Built and tested: the room rules, the port-to-port server and client, aliases, channels, and
the terminal commands. 37 tests in Xmsg.Chat plus the terminal-door tests in
TwoNodeTerminalTests.
CORRECTED 2026-08-25 - this section was stale and said the opposite of the truth. It read "Nobody has yet sat at a real SINTRAN terminal, joined the room and talked to a second user."
That was overtaken long ago, by the PLANC implementation rather than this one. Two users have talked on two different ND-100s; the rooms now span three machines over trunks, with a machine in the middle relaying between two that have no trunk between them, and the whole thing starts from a cold boot with nothing typed. See CHAT-APP-SPECIFICATION.md.
What remains true here is that the C# library's own terminal door has not had that live run.
A known limit of the test harness, not of the server: it cannot drive two commands on one
session - the second renders nothing. The output window is held by the previous reply's final
frame, which the test client never acknowledges. Measured with a control (who then stat) that
has nothing to do with chat. See OutputWindowDiagnosticTests for what is proven and what is
still open.
Not built: persistent rooms (a room exists while the server object does), history (a joiner
sees nothing said before they arrived), and private messages between two members - though the
terminal tell command already does person-to-person messaging outside the room.
tell IS THIS LIBRARY'S, NOT THE SINTRAN SERVER'S. CHATSV.PLNC has no person-to-person
message kind at all - its kinds are 1-16 for room traffic, 32-38 for admin and 48-53 for trunks.
Anyone reading this page and expecting tell to work on an ND-100 will not find it.