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.
