mud/docs/how/if-terminal.txt

152 lines
4.4 KiB
Text

if terminal — how the interactive fiction system works
======================================================
what it is
==========
players can play z-machine interactive fiction games (zork, etc) from inside
the mud. input routes to a dfrotz subprocess instead of the command dispatcher.
other players in the room see the output scrolling on a virtual terminal.
this is level 1 from if-journey.rst — the IF world is a black box. we pipe
text in and out. no interpreter embedding, no VM introspection.
how it works
============
three pieces: the session, the command, and the server integration.
IFSession (if_session.py)
-------------------------
manages a single dfrotz subprocess. one session per player.
spawns dfrotz with:
dfrotz -p -w 80 -m <story_path>
-p plain ASCII (no formatting escapes)
-w 80-column width
-m no MORE prompts (continuous output)
IFResponse dataclass carries output + done flag back to caller.
input flow:
player types something
-> server routes to if_session.handle_input()
-> escape commands (::quit, ::save, ::help) handled at mud layer
-> everything else written to dfrotz stdin
-> response read from dfrotz stdout
-> IFResponse returned to server
reading output:
dfrotz writes text then shows a ">" prompt and waits for input.
_read_response() reads in 1024-byte chunks until it detects the prompt.
prompt detection checks for "> ", ">\n", ">\r\n" at end of raw bytes,
and "\n>" in the stripped text. bare ">" with no preceding newline
is only stripped when the raw bytes confirm a prompt pattern.
idle timeout: 100ms (returns once data stops flowing)
overall deadline: 5 seconds per response
this was tricky to get right — see docs/lessons/dfrotz-prompt-detection.txt
escape commands
---------------
prefixed with :: to avoid conflicting with game input.
::quit exit game (auto-saves first)
::save force save to disk
::help show available escape commands
play command (commands/play.py)
-------------------------------
entry point. registered as a normal-mode command.
play list available stories
play zork start zork (exact or prefix match)
story discovery: scans content/stories/ for .z3, .z5, .z8, .zblorb files.
prefix matching: "zork" matches "zork1.z3".
on start:
1. create IFSession with story path
2. spawn dfrotz, read intro text
3. push "if" onto player.mode_stack
4. if save file exists, auto-restore
5. broadcast intro/restored text to spectators
server integration (server.py)
------------------------------
the shell loop checks player.mode and routes input:
if player.mode == "if" and player.if_session:
route to if_session.handle_input()
on done (::quit):
stop session, pop mode stack, send "you leave the terminal."
on disconnect:
session cleaned up in finally block (auto-saves)
spectator broadcasting
======================
broadcast_to_spectators() in if_session.py iterates the global players
dict, finds everyone at the same (x,y) who isn't the playing player,
sends them formatted output:
[Jared's terminal]
> open mailbox
Opening the small mailbox reveals a leaflet.
triggered on: game start/restore, every game output, and when the
player leaves the terminal.
save/restore
============
saves stored at: data/if_saves/{player_name}/{game_name}.qzl
player names sanitized (non-alphanumeric chars replaced with _).
quetzal format (.qzl) — z-machine standard.
save flow:
send "save\n" to dfrotz -> read filename prompt -> send path ->
read confirmation. auto-confirms "overwrite" if file exists.
checks for "Failed" in response.
restore flow:
on "play zork" with existing save file, sends "restore\n" to dfrotz,
provides save path, reads restored game text.
double-save prevention:
_saved flag prevents saving twice (::quit triggers save, then stop()
would save again). flag resets on regular game input.
file layout
===========
src/mudlib/if_session.py IFSession class + broadcast_to_spectators
src/mudlib/commands/play.py play command
content/stories/zork1.z3 bundled story file
data/if_saves/ per-player save files (gitignored)
what's not here (yet)
=====================
- terminal room objects (currently just the "play" command)
- embedded interpreter (levels 2-5)
- multiplayer z-machine
- ghost presence in IF rooms
see if-journey.rst for the bigger picture.