Screen Reader Support with MUSHclient and Mudlet scripting
Text-based MUDs present unique challenges for screen reader users: scrolling combat spam, visually structured prompts that read poorly when linearized, and ASCII art interpreted as gibberish. This guide provides concrete implementation steps for developers and players to restructure output, implement audio cues, and configure clients to work efficiently with NVDA and JAWS. Focus is on reducing speech verbosity while preserving critical gameplay information through non-visual channels.

Audit Output Verbosity with Speech Viewer
Install NVDA and enable the Speech Viewer (NVDA menu -> Tools -> Speech Viewer) to observe exactly how your MUD's output is vocalized. Connect to your MUD and perform common actions: move between rooms, enter combat, check inventory, and read channels. Document which patterns cause excessive verbosity, such as prompts being read after every line, ASCII maps spelled letter-by-letter, or status bars read as numbers without context. Log specific line patterns that interrupt flow.
⚠ Common Pitfalls
- •Assuming visual layout equals spoken layout
- •Testing only with synthesized speech rates below 50%
- •Failing to test with punctuation verbosity set to 'most' in NVDA
Restructure Server-Side Prompts for Linear Parsing
Modify your MUD's prompt output to present all status variables on a single line with unambiguous separators. Avoid multi-column layouts or visual ASCII bars. If using GMCP, ensure the 'char' package sends discrete values for 'hp', 'mp', and 'mv'. For non-GMCP clients, enforce format: '[HP:120/150 MP:80/100 MV:90%] > ' which screen readers parse linearly without confusing pauses. Place this prompt at the end of output, not the beginning, to prevent interruption of room descriptions.
-- Mudlet prompt trigger capturing structured data
-- Pattern: ^\[HP:(\d+)/(\d+) MP:(\d+)/(\d+) MV:(\d+)%\] > $
local hp_cur, hp_max = tonumber(matches[2]), tonumber(matches[3])
local mp_cur, mp_max = tonumber(matches[4]), tonumber(matches[5])
local mv_pct = tonumber(matches[6])
-- Audio cue for critical health
if hp_cur / hp_max < 0.25 then
playSoundFile("low_health.wav")
end
-- Store for GMCP-less status bar
player.hp = hp_cur
player.max_hp = hp_max⚠ Common Pitfalls
- •Using color codes that insert silence characters or control codes
- •Placing prompt at top of output which breaks reading flow
- •Using parentheses or brackets that NVDA interprets as 'left paren' repeatedly
Implement Audio Cue Triggers for Combat Events
Create triggers that match combat text (incoming hits, outgoing damage, enemy deaths) and play distinct audio files rather than relying on visual scroll parsing. In Mudlet, use playSoundFile() with short WAV files under 0.5 seconds. Map different sounds to incoming damage types (slashing vs magic), enemy death knells, and completion of timed actions like crafting or casting. Configure these triggers to delete or gag the original line after playing audio to reduce speech load, keeping only critical numerical data.
-- Combat audio feedback with line suppression
-- Pattern: You attack (.+) with your (.+) and (.+)!\nYou deal (\d+) damage\.
local damage = tonumber(matches[5])
if damage > 50 then
playSoundFile("heavy_hit.wav")
elseif damage > 20 then
playSoundFile("medium_hit.wav")
else
playSoundFile("light_hit.wav")
end
-- Remove from screen reader buffer but keep in log
deleteLine()
-- Output condensed form for audio confirmation only
echo("Hit for " .. damage .. "\n")⚠ Common Pitfalls
- •Using audio files longer than 0.5s that overlap during fast combat
- •Triggering on partial patterns causing audio spam
- •Deleting lines that contain experience gain or loot notifications
Script Channel Gagging and Priority Queues
Build toggles that temporarily hide non-critical channels (chat, social, gossip) during combat or navigation. In Mudlet, maintain a Lua table of allowed channels based on current mode ('combat', 'explore', 'chat'). Create triggers that match channel patterns (e.g., '^\[(\w+)\]') and delete lines where the captured channel is not in the allowed list. Provide function keys (F1-F4) to instantly switch modes without opening menus.
-- Channel priority filter system
channelModes = {
combat = {combat = true, tell = true, say = true},
explore = {tell = true, say = true, clan = true},
chat = {chat = true, tell = true, say = true, clan = true}
}
currentMode = "explore"
function filterChannelLine()
local line = getCurrentLine()
local channel = line:match("^\[(\w+)\]")
if channel and not channelModes[currentMode][channel:lower()] then
deleteLine()
-- Optional: Copy to secondary buffer for later review
end
end
-- Bind to F1: setMode("combat") etc.⚠ Common Pitfalls
- •Gagging system messages containing combat round timers
- •Creating modes too granular to switch quickly
- •Failing to provide audio confirmation of mode switches
Add Navigation Landmarks to Room Output
Ensure room descriptions provide predictable landmarks for orientation. If server-side changes are impossible, use client triggers to capture the room name and exits, then output a condensed summary line at the top of the buffer: 'Location: Temple Square. Exits: North, East, South.' This provides a speakable anchor before the lengthy description. Configure triggers to fire on the room name line (usually capitalized and standalone) and the exits line, storing these values to prepend to the next output block.
-- Room landmark generator
roomData = {name = "", exits = ""}
-- Trigger 1: ^([A-Z][\w\s]+)$ (Room name)
roomData.name = matches[2]
-- Trigger 2: ^\[?Exits:\s*([\w\s,]+)\.?\]?$
roomData.exits = matches[2]
-- Output landmark before description
if roomData.name ~= "" and roomData.exits ~= "" then
cecho("<cyan>Location: " .. roomData.name .. ". Exits: " .. roomData.exits .. "\n")
roomData = {name = "", exits = ""} -- Reset
end⚠ Common Pitfalls
- •Moving exits to top without indicating this is client-side modification
- •Breaking automappers that rely on standard room format order
- •Creating triggers that fire on player speech containing exit names
Validate with Screen Reader Only Playtesting
Disable your monitor or close your eyes and attempt a 30-minute play session using only NVDA or JAWS at normal speed. Navigate between three distinct areas, engage in combat with audio cues active, manage inventory using only the keyboard, and communicate on channels using your filter modes. Document any point where you must rely on visual memory, where speech output is ambiguous (similar sounding room names), or where audio cues overlap. Refine triggers and adjust audio file volumes based on this session.
⚠ Common Pitfalls
- •Testing with sighted assistance still available as fallback
- •Using only automated testing tools instead of actual screen reader interaction
- •Testing only static rooms without movement or combat
What you built
Screen reader accessibility requires ongoing iteration as MUD content updates. Maintain a separate configuration profile for accessibility testing and document any server-side output changes affecting prompt structure or room formatting. Establish a feedback channel with blind players to prioritize which audio cues and filters provide the most benefit, and version your client scripts to track compatibility with screen reader updates.