Add a terminal plan
This commit is contained in:
parent
4b52051bed
commit
bc5e829f6b
1 changed files with 264 additions and 0 deletions
264
docs/plans/if-terminal.txt
Normal file
264
docs/plans/if-terminal.txt
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
if-terminal — playing interactive fiction from the mud
|
||||
======================================================
|
||||
|
||||
goal: sit at a terminal in the mud, boot up zork, play it. other players
|
||||
in the room see text scrolling. single player for now, but designed so
|
||||
spectating and "follow" interactions aren't foreclosed.
|
||||
|
||||
references:
|
||||
docs/how/if-journey.rst — five levels of integration, interpreter audits
|
||||
docs/how/if-integration.txt — original research, subprocess vs embed analysis
|
||||
docs/how/mojozork-audit.rst — multiplayer z-machine proof (reference only)
|
||||
docs/how/viola-embedding-audit.rst — viola interpreter audit
|
||||
docs/how/zvm-embedding-audit.rst — zvm interpreter audit
|
||||
|
||||
|
||||
what "playing zork" means
|
||||
=========================
|
||||
|
||||
a player walks into a room. there's something there — a terminal, a cabinet,
|
||||
a glowing orb, whatever. they type a command to start playing. their input
|
||||
now routes to dfrotz (z-machine interpreter) instead of the mud command
|
||||
dispatcher. they're IN zork.
|
||||
|
||||
they're still a mud entity in the room. they haven't vanished. other players
|
||||
see them sitting at the terminal. when they quit zork, they're back to normal
|
||||
mud mode. their zork save persists.
|
||||
|
||||
|
||||
the spectator question
|
||||
======================
|
||||
|
||||
when player A is playing zork, player B walks into the room and sees:
|
||||
|
||||
Jared is here, hunched over a glowing terminal.
|
||||
|
||||
> open mailbox
|
||||
Opening the small mailbox reveals a leaflet.
|
||||
|
||||
player B sees the output scrolling. they're spectating. they didn't choose
|
||||
to — they just see it because they're in the room.
|
||||
|
||||
this is level 1 from if-journey.rst. the IF world is opaque (we don't know
|
||||
what room the player is in, we can't inject items), but the TEXT is visible.
|
||||
subprocess dfrotz, text piped in/out, broadcast to room.
|
||||
|
||||
|
||||
the follow question
|
||||
===================
|
||||
|
||||
"follow <player>" doesn't exist yet. when it does:
|
||||
|
||||
- following player A means you auto-move when they move between rooms
|
||||
- if A sits at a terminal and enters zork, you DON'T auto-enter zork
|
||||
- you're in the same room, you see the text (spectator mode)
|
||||
- if A gets up from the terminal and walks away, you follow them out
|
||||
|
||||
this is the right default. entering zork is a deliberate choice, not
|
||||
something that happens because you're following someone.
|
||||
|
||||
future possibility (NOT this plan): two players both playing zork in the
|
||||
same room. each has their own dfrotz process. each sees their own game.
|
||||
they see "ghost" indicators of each other — "a faint shimmer suggests
|
||||
Jared is nearby" — but can't truly interact through zork's parser. this
|
||||
is cosmetic multiplayer, not real shared-world (level 4). it's achievable
|
||||
with level 1 architecture: just track which IF room each player is in
|
||||
(parse the room name from output) and show ghosts when rooms match. a
|
||||
fun enhancement that doesn't require embedded interpreters.
|
||||
|
||||
|
||||
what we're building
|
||||
===================
|
||||
|
||||
phase 0 — prerequisites
|
||||
------------------------
|
||||
|
||||
- install dfrotz (user action: sudo dnf install frotz or equivalent)
|
||||
- zork1.z3 is already at content/stories/zork1.z3 (from mojozork, free release)
|
||||
- verify dfrotz can run the story file standalone
|
||||
|
||||
|
||||
phase 1 — if session handler
|
||||
------------------------------
|
||||
|
||||
the core. equivalent of editor.py but for interactive fiction.
|
||||
|
||||
new file: src/mudlib/if_session.py
|
||||
|
||||
IFSession class:
|
||||
process: asyncio.subprocess.Process (dfrotz child)
|
||||
player: Player (who's playing)
|
||||
spectators: list[Player] (others in room, updated dynamically)
|
||||
story_path: str (path to .z3/.z5 file)
|
||||
game_name: str ("zork1")
|
||||
save_dir: str (per-player save directory)
|
||||
|
||||
async start():
|
||||
spawn dfrotz as async subprocess (stdin=PIPE, stdout=PIPE, stderr=PIPE)
|
||||
read initial output (game intro text)
|
||||
broadcast intro to player + spectators
|
||||
|
||||
async handle_input(text: str) -> IFResponse:
|
||||
if text is a mud escape command (e.g. "::quit", "::save"):
|
||||
handle at mud layer (quit session, force save, etc)
|
||||
else:
|
||||
write text to dfrotz stdin
|
||||
read response from dfrotz stdout
|
||||
broadcast response to player + spectators
|
||||
return IFResponse(output=response, done=False)
|
||||
|
||||
on "::quit" or dfrotz process exit:
|
||||
return IFResponse(output=goodbye_text, done=True)
|
||||
|
||||
async stop():
|
||||
save game state (send "save" to dfrotz, capture file)
|
||||
terminate dfrotz process
|
||||
cleanup
|
||||
|
||||
dfrotz flags:
|
||||
-p (plain text, no formatting escapes)
|
||||
-w N (screen width, match terminal or default 80)
|
||||
possibly -h N (screen height)
|
||||
|
||||
escape prefix "::":
|
||||
::quit — leave the terminal, return to mud
|
||||
::save — force save (dfrotz save command)
|
||||
::help — show escape commands
|
||||
regular input goes straight to dfrotz
|
||||
|
||||
|
||||
phase 2 — mode stack integration
|
||||
----------------------------------
|
||||
|
||||
wire IFSession into the existing mode stack, following the editor pattern.
|
||||
|
||||
server.py shell loop addition:
|
||||
|
||||
elif player.mode == "if" and player.if_session:
|
||||
response = await player.if_session.handle_input(inp)
|
||||
if response.output:
|
||||
await player.send(response.output)
|
||||
if response.done:
|
||||
player.if_session = None
|
||||
player.mode_stack.pop()
|
||||
|
||||
player.py addition:
|
||||
|
||||
if_session: IFSession | None = None
|
||||
|
||||
entering IF mode:
|
||||
|
||||
player.if_session = IFSession(player, story_path, ...)
|
||||
await player.if_session.start()
|
||||
player.mode_stack.append("if")
|
||||
|
||||
|
||||
phase 3 — the play command
|
||||
----------------------------
|
||||
|
||||
how a player starts a game. for now, a simple command. terminal objects
|
||||
come later.
|
||||
|
||||
play zork
|
||||
|
||||
this:
|
||||
1. looks up "zork" in available stories (content/stories/)
|
||||
2. creates IFSession with the story file
|
||||
3. pushes "if" mode onto mode stack
|
||||
4. player sees zork's intro text
|
||||
|
||||
later this becomes "use terminal" or an interaction with a room object.
|
||||
for now, a direct command keeps things simple and testable.
|
||||
|
||||
|
||||
phase 4 — spectator broadcasting
|
||||
----------------------------------
|
||||
|
||||
when a player is in IF mode, other players in the same room see output.
|
||||
|
||||
on IFSession output:
|
||||
send to player (the one playing)
|
||||
for each other player in the same room:
|
||||
send formatted version:
|
||||
[Jared's terminal]
|
||||
> open mailbox
|
||||
Opening the small mailbox reveals a leaflet.
|
||||
|
||||
spectator list updates when players enter/leave the room.
|
||||
|
||||
"look" in the room shows:
|
||||
Jared is here, playing on a glowing terminal.
|
||||
|
||||
this needs the room-local broadcasting pattern mentioned in if-integration.txt.
|
||||
it's the first use case for ambient output in a room.
|
||||
|
||||
|
||||
phase 5 — save/restore persistence
|
||||
------------------------------------
|
||||
|
||||
player's zork progress persists across sessions.
|
||||
|
||||
- saves stored per-player per-game (e.g. saves/jared/zork1.sav)
|
||||
- on "play zork": check for existing save, offer to restore
|
||||
- on "::quit": auto-save before exiting
|
||||
- dfrotz handles save/restore natively via its save command
|
||||
- we just need to manage the save file locations
|
||||
|
||||
could use SQLite blobs (like the persistence doc suggests) or just
|
||||
filesystem saves. filesystem is simpler for dfrotz since it already
|
||||
writes save files. start with filesystem.
|
||||
|
||||
|
||||
what we're NOT building (yet)
|
||||
=============================
|
||||
|
||||
- embedded interpreter (levels 2-5). dfrotz subprocess is the whole story.
|
||||
- multiplayer z-machine. one player per dfrotz process.
|
||||
- terminal room objects. "play" command is the entry point for now.
|
||||
- follow command. not in scope but the design accommodates it.
|
||||
- ghost presence in IF rooms. cool idea, deferred.
|
||||
- item bridge between IF and mud worlds. level 5, way out.
|
||||
- non-zork games. architecture supports any z-machine game but we test
|
||||
with zork1 only.
|
||||
|
||||
|
||||
open questions
|
||||
==============
|
||||
|
||||
1. dfrotz output reading
|
||||
dfrotz doesn't have a clean "here's one response" delimiter. it writes
|
||||
to stdout and then waits for input. we need to detect when it's done
|
||||
writing. options: read with timeout, look for the ">" prompt, or use
|
||||
expect-style pattern matching. needs experimentation.
|
||||
|
||||
2. save file management
|
||||
dfrotz save files are opaque blobs. where do we store them? how do we
|
||||
name them? per-player directory under a saves/ dir? or blob them into
|
||||
sqlite?
|
||||
|
||||
3. screen width
|
||||
dfrotz formats text to a width. do we detect the player's terminal
|
||||
width (NAWS) and pass it to dfrotz? or use a fixed width? if the
|
||||
player resizes, can we tell dfrotz?
|
||||
|
||||
4. escape prefix
|
||||
"::" is arbitrary. could conflict with game input (unlikely for IF
|
||||
but possible). alternatives: ".", "/", or a control character.
|
||||
|
||||
5. spectator bandwidth
|
||||
if 10 people are in the room, every zork output goes to 10 players.
|
||||
is this a performance concern? probably not at our scale, but worth
|
||||
noting.
|
||||
|
||||
|
||||
estimated phases and what each delivers
|
||||
========================================
|
||||
|
||||
phase 0: can run zork standalone in a terminal
|
||||
phase 1-2: can play zork inside the mud ("play zork", input routed, "::quit" to leave)
|
||||
phase 3: "play zork" is a real command, discoverable
|
||||
phase 4: others in the room see the game text
|
||||
phase 5: progress saves between sessions
|
||||
|
||||
phases 1-3 are the MVP. a player can sit in the mud, play zork, and quit.
|
||||
phases 4-5 make it social and persistent.
|
||||
Loading…
Reference in a new issue