---
name: mainline-display
description: Display backend implementation and the Display protocol
compatibility: opencode
metadata:
  audience: developers
  source_type: codebase
---

## What This Skill Covers

This skill covers Mainline's display backend system - how to implement new display backends and how the Display protocol works.

## Key Concepts

### Display Protocol

All backends implement a common Display protocol (in `engine/display/__init__.py`):

```python
class Display(Protocol):
    width: int
    height: int
    
    def init(self, width: int, height: int, reuse: bool = False) -> None:
        """Initialize the display"""
        ...
    
    def show(self, buf: list[str], border: bool = False) -> None:
        """Display the buffer"""
        ...
    
    def clear(self) -> None:
        """Clear the display"""
        ...
    
    def cleanup(self) -> None:
        """Clean up resources"""
        ...
    
    def get_dimensions(self) -> tuple[int, int]:
        """Return (width, height)"""
        ...
```

### DisplayRegistry

Discovers and manages backends:

```python
from engine.display import DisplayRegistry
display = DisplayRegistry.create("terminal")  # or "websocket", "null", "multi"
```

### Available Backends

| Backend | File | Description |
|---------|------|-------------|
| terminal | backends/terminal.py | ANSI terminal output |
| websocket | backends/websocket.py | Web browser via WebSocket |
| null | backends/null.py | Headless for testing |
| multi | backends/multi.py | Forwards to multiple displays |
| moderngl | backends/moderngl.py | GPU-accelerated OpenGL rendering (optional) |

### WebSocket Backend

- WebSocket server: port 8765
- HTTP server: port 8766 (serves client/index.html)
- Client has ANSI color parsing and fullscreen support

### Multi Backend

Forwards to multiple displays simultaneously - useful for `terminal + websocket`.

## Adding a New Backend

1. Create `engine/display/backends/my_backend.py`
2. Implement the Display protocol methods
3. Register in `engine/display/__init__.py`'s `DisplayRegistry`

Required methods:
- `init(width: int, height: int, reuse: bool = False)` - Initialize display
- `show(buf: list[str], border: bool = False)` - Display buffer
- `clear()` - Clear screen
- `cleanup()` - Clean up resources
- `get_dimensions() -> tuple[int, int]` - Get terminal dimensions

Optional methods:
- `title(text: str)` - Set window title
- `cursor(show: bool)` - Control cursor

## Usage

```bash
python mainline.py --display terminal  # default
python mainline.py --display websocket
python mainline.py --display moderngl  # GPU-accelerated (requires moderngl)
```

## Common Bugs and Patterns

### BorderMode.OFF Enum Bug

**Problem**: `BorderMode.OFF` has enum value `1` (not `0`), and Python enums are always truthy.

**Incorrect Code**:
```python
if border:
    buffer = render_border(buffer, width, height, fps, frame_time)
```

**Correct Code**:
```python
from engine.display import BorderMode
if border and border != BorderMode.OFF:
    buffer = render_border(buffer, width, height, fps, frame_time)
```

**Why**: Checking `if border:` evaluates to `True` even when `border == BorderMode.OFF` because enum members are always truthy in Python.

### Context Type Mismatch

**Problem**: `PipelineContext` and `EffectContext` have different APIs for storing data.

- `PipelineContext`: Uses `set()`/`get()` for services
- `EffectContext`: Uses `set_state()`/`get_state()` for state

**Pattern for Passing Data**:
```python
# In pipeline setup (uses PipelineContext)
ctx.set("pipeline_order", pipeline.execution_order)

# In EffectPluginStage (must copy to EffectContext)
effect_ctx.set_state("pipeline_order", ctx.get("pipeline_order"))
```

### Terminal Display ANSI Patterns

**Screen Clearing**:
```python
output = "\033[H\033[J" + "".join(buffer)
```

**Cursor Positioning** (used by HUD effect):
- `\033[row;colH` - Move cursor to row, column
- Example: `\033[1;1H` - Move to row 1, column 1

**Key Insight**: Terminal display joins buffer lines WITHOUT newlines, relying on ANSI cursor positioning codes to move the cursor to the correct location for each line.

### EffectPluginStage Context Copying

**Problem**: When effects need access to pipeline services (like `pipeline_order`), they must be copied from `PipelineContext` to `EffectContext`.

**Pattern**:
```python
# In EffectPluginStage.process()
# Copy pipeline_order from PipelineContext services to EffectContext state
pipeline_order = ctx.get("pipeline_order")
if pipeline_order:
    effect_ctx.set_state("pipeline_order", pipeline_order)
```

This ensures effects can access `ctx.get_state("pipeline_order")` in their process method.
