forked from genewildish/Mainline
feat(integration): Complete feature rewrite with pipeline architecture, effects system, and display improvements
Major changes: - Pipeline architecture with capability-based dependency resolution - Effects plugin system with performance monitoring - Display abstraction with multiple backends (terminal, null, websocket) - Camera system for viewport scrolling - Sensor framework for real-time input - Command-and-control system via ntfy - WebSocket display backend for browser clients - Comprehensive test suite and documentation Issue #48: ADR for preset scripting language included This commit consolidates 110 individual commits into a single feature integration that can be reviewed and tested before further refinement.
This commit is contained in:
163
.opencode/skills/mainline-display/SKILL.md
Normal file
163
.opencode/skills/mainline-display/SKILL.md
Normal file
@@ -0,0 +1,163 @@
|
||||
---
|
||||
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.
|
||||
Reference in New Issue
Block a user