Section: What we are building.
Run wesnothd.exe on your own computer, connect a client named ChatBot, and make it answer \wave with Hello! in the lobby.
This is not a toy protocol. It uses the exact flow we reversed from Wesnoth 1.14.9:
Keep transport, decoding, session meaning, and behavior as separate layers. One incoming message should travel through the pipeline before any reply is built:
This separation lets a malformed frame fail before it reaches chat behavior, and it lets the command handler work with events instead of raw network bytes:

Section: Connect with timeouts.
Before reading protocol bytes, the client needs a connection that cannot silently drift to another host or wait forever for a stalled local server. connect_local accepts one socket address, checks that it is the course loopback endpoint, then sets finite connect, read, and write waits:
Code example. The surrounding prose explains this example; the full lesson contains the exact code.
In brief: A timeout bounds how long the bot can wait; a verified endpoint bounds whom it talks to.
Section: Read and write exact frames.
Lesson 9.4’s encode_wesnoth_frame performs the gzip compression and adds the four-byte big-endian length. write_all matters because one write may send only part of a buffer.
The two helpers implement the strip in opposite directions. send_wml frames text and writes all bytes; read_frame reads the four-byte length, checks the cap, then reads that many payload bytes before a decoder sees them. A short T C P, transmission control protocol, read is normal, so both paths use the exact-read/write operations:
Code example. The surrounding prose explains this example; the full lesson contains the exact code.
Code example. The surrounding prose explains this example; the full lesson contains the exact code.
read_exact handles T C P fragmentation. It does not assume one receive call equals one Wesnoth message.
Section: Handle early server replies.
The opening negotiation is special, so collect its replies until a short quiet period. The historical capture received 41 bytes.
Alternative: Treat the connection like a conversation: inspect the reply before deciding which question is appropriate next.
This quiet-period reader is only for the unframed negotiation reply. It collects successive T C P reads until the server closes or pauses briefly, while the size cap prevents an unexpected reply from growing forever. After login, read_frame uses the protocol’s length field instead:
Code example. The surrounding prose explains this example; the full lesson contains the exact code.
Keep this helper for negotiation only. Once the session is in the lobby, use the frame reader.
Section: Perform the Wesnoth 1.14.9 login.
The parser and frame writer now have a concrete job. The course server first expects four zero negotiation bytes, then a version message and a login message in compressed W M L frames. log_in sends them in that order and reads each reply before advancing; a nickname is validated before it enters W M L:
Code example. The code’s comments explain: The first message is not compressed or length-prefixed.
If the server rejects the version, compare the decompressed frame with the one produced by the official 1.14.9 client. Do not randomly change compressed bytes; fix the W M L and recompress it.
Section: Answer \wave.
Once the login succeeds, the client announces itself and loops over framed server messages. A decoded \wave event will produce the reply described above; ordinary messages leave the connection running:
Code example. The surrounding prose explains this example; the full lesson contains the exact code.
Section: Prove the bot works.
Start wesnothd.exe.
Run the client with address 127.0.0.1:15000 and nickname ChatBot.
Join localhost:15000 from the normal Wesnoth client.
Type \wave in the lobby.
Confirm Hello! appears from ChatBot.
That visible lobby response proves the real negotiation, version, login, gzip framing, Simple W M L, receive loop, and command logic all work together.
Section: Keep it well behaved.
Limit message length, respond at most once per command frame, add a short send rate limit, and exit cleanly when the local server closes. A parser error should print the frame size and stop; it should not enter an automatic reconnect storm.
Narration: A reply rate limit prevents one repeated command from filling the lobby with messages.
Section: Run the complete bot.
After building the workspace, start the course wesnothd.exe on port 15000 and run:
Terminal window
Code example. The surrounding prose explains this example; the full lesson contains the exact code.
The full client is wesnoth_chatbot.rs. The framing/parser it imports is wesnoth_protocol.rs. Together they implement the real loopback connection, timeouts, four-zero negotiation, version frame, login frame, lobby receive loop, \wave recognition, and Hello! reply.
Additional detail: Complete lab source: wesnoth_chatbot.rs.
Code example. The surrounding prose explains this example; the full lesson contains the exact code.
The reusable gzip/W M L framing stays in one tested protocol module; this file owns the session state. That split is intentional: byte framing should not know what \wave means, and chat behavior should not reimplement gzip.
Silent video demonstration: An external client answering Wesnoth chat. The original external-client demonstration shows a chat command receiving a reply. The visible exchange depends on the earlier connection, negotiation, login, frame decoding, and command handling; a valid message alone is not a complete client.
This bot owns its own server connection. Lesson 9.7 puts a relay between the official client and the same local server, so we can observe an existing conversation before choosing one bounded change.