Files
sideline/.opencode/skills/mainline-display/SKILL.md
David Gwilliam ef98add0c5 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.
2026-03-20 04:41:44 -07:00

4.6 KiB

name, description, compatibility, metadata
name description compatibility metadata
mainline-display Display backend implementation and the Display protocol opencode
audience source_type
developers 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):

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:

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

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:

if border:
    buffer = render_border(buffer, width, height, fps, frame_time)

Correct Code:

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:

# 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:

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:

# 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.