The Three Layers
DialogProcessor is the engine - it only knows about IDialogDisplay, nothing about UI or DialogManager. DialogManager is the UI-facing singleton - implements IDialogDisplay, and StartDialog() is the only entry point anyone outside this folder should call. DialogPhase (in Simulation/) is a thin wrapper so a dialog can be one step in a turn loop.
Execution Loop
DialogProcessor.Run( start ) repeats current = await current.Run( display ) until current is null. Each DialogBlock decides its own next step; the base class default is simply "return next" (linear flow), and only PlayerChoice / DialogBranch override that to branch.
Block-by-Block Behaviour
DialogLine - calls display.Show(this), awaits it, returns next.
PlayerChoice - calls display.Show(this), reads answer.selectedOptionIndex, runs that option's action if set, returns that option's next.
DialogBranch - no display call. Returns branch if condition.Evaluate() is true, else the inherited next.
DialogList - runs its own blocks in flat order, ignoring each child's own "next" while inside the list, then returns its own next. This is an assumption, not confirmed - see outline/tech "Implementation Status". Practical effect: don't put a PlayerChoice/DialogBranch directly inside a DialogList's blocks, its branch target will be silently discarded.
ReturnBlock - always returns null, ending the whole dialog (even if nested inside a DialogList).
CommentBlock - editor-only, no override, just falls through to next like the default.