diff --git a/include/bitbishop/interface/readme.md b/include/bitbishop/interface/readme.md
index b30e333..8129080 100644
--- a/include/bitbishop/interface/readme.md
+++ b/include/bitbishop/interface/readme.md
@@ -4,9 +4,14 @@
`interface/` is the **boundary between BitBishop and the outside world**.
-In the current codebase this directory is **primarily the UCI layer**. It parses
-commands, owns the session loop, translates time controls into search limits,
-and reports results back to the caller.
+In the current codebase this directory is **primarily the UCI layer**.
+
+It is responsible for:
+
+- **Parsing external commands** and inputs
+- **Translating protocol concepts into engine calls**
+- **Managing search sessions**, clocks, and stop requests
+- **Emiting protocol-compliant responses**
## Place in the architecture
@@ -38,19 +43,218 @@ flowchart TD
Engine --> Other
```
-## Responsibilities
+### Thread Lifecycle (runtime)
+
+Interface defines threading concepts in order to work with the UCI protocol.
+
+There are currently three threads:
+
+- The **main thread (control thread)**: parsing commands, polling search reports, and writing protocol output.
+- The **listener thread** (`UciCommandChannel`) reads incoming command lines from the input stream.
+- The **worker thread** (`SearchWorker`) handles best move search and currently works alone.
+
+```mermaid
+sequenceDiagram
+ autonumber
+ participant IO as GUI/CLI
stdin/stdout
+ participant In as Input Stream
std::cin (default)
+ participant Main as Main Thread
UciEngine::loop()
+ participant Cmd as Command Thread
UciCommandChannel::reader_loop()
+ participant Ch as Channel State
pending_lines + lines_cv
+ participant Session as SearchSession
(main thread)
+ participant Worker as Search Thread
SearchWorker::run()
+
+ Main->>Cmd: command_channel.start()
+ loop Reader lifecycle
+ Cmd->>In: std::getline(input_stream, line)
+ Note right of Cmd: Blocking read (default runtime: std::cin).
Sleeps until newline or EOF.
+ alt Line read
+ Cmd->>Ch: push line + notify_one()
+ else EOF reached
+ Cmd->>Ch: eof_reached = true + notify_all()
+ end
+ end
+
+ loop Main loop tick (while is_running)
+ Main->>Session: poll()
+ Main->>Ch: wait_and_pop_line(timeout = 5ms)
+ Note right of Main: Blocks on condition_variable::wait_for
when queue is empty.
+ alt Command line available
+ Main->>Main: split(raw_line)
+ Main->>Main: command_registry.dispatch(tokens)
+ alt go / bench
+ Main->>Session: start_go(...) or start_bench(...)
+ Session->>Worker: start()
+ loop Search execution
+ Worker-->>Session: push_report(Iteration/Finish)
+ Main->>Session: poll()
+ Session->>Session: emit_reports()
+ Session-->>IO: reporter output (bestmove / bench)
+ end
+ else stop
+ Main->>Session: request_stop()
+ Session->>Worker: stop_flag = true
+ else quit
+ Main->>Session: request_stop()
+ Session->>Worker: stop_flag = true
+ Main->>Main: is_running = false
+ Note right of Main: Loop guard will fail on next check.
Final cleanup runs after loop.
+ end
+ else No command within timeout
+ Main->>Main: wake up and continue loop
+ end
+ alt Input reached EOF
+ Main->>Session: request_stop()
+ Main->>Session: poll() until idle
+ Main->>Session: stop_and_join()
+ Main->>Cmd: command_channel.stop()
+ Main-->>IO: loop exits
+ end
+ end
+```
-- **Parse external commands** and inputs
-- **Translate protocol concepts into engine calls**
-- **Manage search sessions**, clocks, and stop requests
-- **Emit protocol-compliant responses**
+#### Blocking points (intentional idle time)
-## Inputs
+BitBishop does not run every thread at 100% all the time. Some waits are expected and intentional.
+
+| Thread | Blocking call | Blocks when | Wakes up when |
+| ------------------------------------ | -------------------------------------------------------------- | ---------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
+| Command thread (`UciCommandChannel`) | `std::getline(input_stream, line)` | No full line is available (default runtime stream is `std::cin`) | Newline arrives or EOF is reached |
+| Main thread (`UciEngine::loop`) | `wait_and_pop_line(..., 5ms)` (`condition_variable::wait_for`) | Pending line queue is empty and EOF not reached | A line is pushed (`notify_one`), EOF is signaled (`notify_all`), or timeout elapses |
+| Worker thread (`SearchWorker`) | No intentional sleep in `run()` | It is actively searching (CPU-bound) | Search limit reached or `stop_flag` set |
+
+#### `stop` vs `quit` semantics
+
+| Command | Effect on search | Effect on process |
+| ------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------- |
+| `stop` | Requests search interruption (`stop_flag = true`) and keeps the UCI loop alive | Engine keeps running and can accept new commands |
+| `quit` | Requests search interruption (`stop_flag = true`) | Sets `is_running = false`, then exits loop and performs final cleanup (`stop_and_join()`, `command_channel.stop()`) |
+
+> Note: the runtime default input is `std::cin`, but `UciEngine` accepts any `std::istream`. With pre-buffered streams (for example `std::istringstream` in tests), `std::getline` may return immediately.
+
+> Shutdown detail: `UciCommandChannel::stop()` cannot forcibly interrupt a blocking `std::getline`, so the reader thread is detached when EOF is not yet reached.
+
+#### Thread state timeline (runtime)
+
+This complementary view focuses only on thread states, not message payloads.
+
+```mermaid
+stateDiagram-v2
+ direction LR
-- `engine/` for search entry points
-- `moves/` and `Board` for the current game state
-- streams, strings, and threading primitives from the standard library
+ state "Command thread (UciCommandChannel)" as CmdThread {
+ [*] --> BlockedOnInput: start()
+ BlockedOnInput --> RunningPush: line available
+ RunningPush --> BlockedOnInput: push + notify_one
+ BlockedOnInput --> EofReached: EOF
+ EofReached --> [*]
+ }
-## Outputs
+ state "Main thread (UciEngine::loop)" as MainThread {
+ [*] --> RunningControl: while (is_running) start tick
+ RunningControl --> BlockedOnCV: wait_and_pop_line(5ms)
+ BlockedOnCV --> RunningControl: line/EOF/timeout wakeup
+ RunningControl --> RunningControl: dispatch + poll + output
+ RunningControl --> [*]: !is_running (quit OR EOF path)
+ }
-- Stable UCI protocol-compliant responses
+ state "Worker thread (SearchWorker)" as WorkerThread {
+ [*] --> Idle: no active search
+ Idle --> RunningSearch: start_go/start_bench
+ RunningSearch --> RunningSearch: search iterations
+ RunningSearch --> Idle: limit reached or stop_flag
+ }
+```
+
+### Class Relationships (Structure)
+
+```mermaid
+classDiagram
+ direction LR
+
+ class UciEngine {
+ -Board board
+ -Position position
+ -UciCommandChannel command_channel
+ -SearchSession search_session
+ -UciCommandRegistry command_registry
+ -bool is_running
+ +loop()
+ -dispatch(line)
+ -handle_go(line)
+ -handle_bench(line)
+ -handle_stop()
+ -handle_quit()
+ }
+
+ class UciCommandChannel {
+ -input_stream
+ -reader_thread
+ -state
+ +start()
+ +stop()
+ +wait_and_pop_line(line, timeout)
+ +eof()
+ }
+
+ class UciCommandRegistry {
+ -handlers
+ +register_handler(command, handler)
+ +dispatch(line)
+ }
+
+ class SearchSession {
+ -out_stream
+ -worker
+ -reporter
+ +start_go(board, limits)
+ +start_bench(board, limits)
+ +request_stop()
+ +poll()
+ +stop_and_join()
+ }
+
+ class SearchWorker {
+ -worker
+ -stop_flag
+ -finished
+ -reports
+ +start()
+ +request_stop()
+ +drain_reports()
+ +stop()
+ }
+
+ class SearchReporter {
+ <>
+ +on_iteration(best, depth, stats)
+ +on_finish(best, stats)
+ }
+
+ class UciReporter {
+ +on_finish(best, stats)
+ }
+
+ class BenchReporter {
+ +now()
+ +on_finish(best, stats)
+ }
+
+ class SearchLimits
+ class SearchReport
+
+ UciEngine *-- UciCommandChannel
+ UciEngine *-- UciCommandRegistry
+ UciEngine *-- SearchSession
+
+ SearchSession *-- SearchWorker : active search
+ SearchSession *-- SearchReporter : active reporter
+
+ SearchReporter <|.. UciReporter
+ SearchReporter <|.. BenchReporter
+
+ SearchWorker ..> SearchLimits
+ SearchWorker ..> SearchReport
+ SearchSession ..> UciReporter : for go
+ SearchSession ..> BenchReporter : for bench
+```