85 lines
3 KiB
Text
85 lines
3 KiB
Text
dfrotz prompt detection — reading output from a z-machine subprocess
|
|
====================================================================
|
|
|
|
the problem
|
|
===========
|
|
|
|
dfrotz writes game text to stdout, then displays a ">" prompt and waits
|
|
for input. there's no clean "end of response" delimiter — just the prompt
|
|
character appearing at the end of the output.
|
|
|
|
this sounds simple. it wasn't.
|
|
|
|
|
|
what made it hard
|
|
=================
|
|
|
|
1. the prompt format varies:
|
|
- "> " (with trailing space) — most common
|
|
- ">\n" or ">\r\n" — sometimes
|
|
- bare ">" with no preceding newline — edge case but real
|
|
|
|
2. the ">" character can appear IN game text:
|
|
- a bare ">" at end of text isn't always the prompt
|
|
- only strip it when the raw bytes show trailing whitespace after it
|
|
(confirming it's the prompt pattern, not game content)
|
|
|
|
3. chunked I/O:
|
|
- data arrives in arbitrary chunks (not line-by-line)
|
|
- the prompt might be split across chunks
|
|
- can't just check the last character of each read
|
|
|
|
4. timing:
|
|
- after writing a command, dfrotz takes variable time to respond
|
|
- short responses (one line) arrive fast
|
|
- long responses (room descriptions) come in multiple chunks
|
|
- no way to know in advance how much output to expect
|
|
|
|
|
|
what we do
|
|
==========
|
|
|
|
_read_response() in if_session.py uses a chunked reader with dual checks:
|
|
|
|
read up to 1024 bytes per chunk
|
|
after each chunk, check the accumulated buffer for prompt patterns
|
|
idle timeout of 100ms — once data stops flowing, return what we have
|
|
overall deadline of 5 seconds — safety net
|
|
|
|
prompt detection checks (in order):
|
|
1. stripped text ends with "\n>" — always a prompt, strip it
|
|
2. stripped text is just ">" — empty response, return ""
|
|
3. raw bytes end with "> " or ">\n" AND stripped text ends with ">"
|
|
— prompt confirmed by trailing whitespace, strip it
|
|
|
|
the key insight: "\n>" is unambiguously a prompt (game text doesn't end
|
|
that way). bare ">" is ambiguous — only treat it as a prompt when the
|
|
raw bytes have trailing whitespace confirming the prompt pattern.
|
|
|
|
|
|
what we tried that didn't work
|
|
==============================
|
|
|
|
- reading until timeout only: too slow (100ms wait after every response)
|
|
or too fast (miss data that's still coming). need both prompt detection
|
|
AND timeout as fallback.
|
|
|
|
- reading line by line: dfrotz doesn't always end the prompt with a
|
|
newline. sometimes the ">" has a trailing space and nothing else.
|
|
readline() hangs waiting for \n that never comes.
|
|
|
|
- checking only the last character: ">" appears in game text. false
|
|
positives everywhere.
|
|
|
|
|
|
the fix pattern
|
|
===============
|
|
|
|
dual-mode detection: fast path checks for prompt patterns after each
|
|
chunk (instant return when found), slow path falls back to idle timeout
|
|
(catches edge cases where prompt detection fails). overall deadline
|
|
prevents infinite hangs.
|
|
|
|
this pattern works for any subprocess that uses a prompt character to
|
|
signal readiness. the specific prompt patterns are dfrotz-specific, but
|
|
the chunk-accumulate-check loop is reusable.
|