forked from genewildish/Mainline
Add ADRs
35
ADR-001-Capability-Based-Dependency-Resolution.md
Normal file
35
ADR-001-Capability-Based-Dependency-Resolution.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# ADR-001: Capability-Based Dependency Resolution
|
||||
|
||||
**Date:** March 2026
|
||||
**Status:** Accepted
|
||||
|
||||
## Context
|
||||
|
||||
The pipeline needed a way for stages to auto-connect without hardcoding dependencies. Legacy system required explicit stage ordering.
|
||||
|
||||
## Decision
|
||||
|
||||
Stages declare capabilities (what they provide) and dependencies (what they need). Pipeline resolves dependencies using prefix matching.
|
||||
|
||||
```python
|
||||
class DataSourceStage(Stage):
|
||||
capabilities = ["data"] # provides "data"
|
||||
dependencies = [] # no dependencies
|
||||
|
||||
class FontStage(Stage):
|
||||
capabilities = ["rendered"] # provides "rendered"
|
||||
dependencies = ["data"] # needs "data"
|
||||
```
|
||||
|
||||
Prefix matching: dependency "source" matches capability "source.headlines", "source.poetry", etc.
|
||||
|
||||
## Consequences
|
||||
|
||||
- **Positive:** New stages integrate automatically without modifying controller
|
||||
- **Positive:** Flexible composition - swap implementations by changing capabilities
|
||||
- **Negative:** Debugging dependency chains requires understanding prefix resolution
|
||||
|
||||
## References
|
||||
|
||||
- `engine/pipeline/core.py`: Stage base class
|
||||
- `engine/pipeline/controller.py`: Resolution logic
|
||||
35
ADR-002-Stage-Based-Pipeline-Architecture.md
Normal file
35
ADR-002-Stage-Based-Pipeline-Architecture.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# ADR-002: Stage-Based Pipeline Architecture
|
||||
|
||||
**Date:** March 2026
|
||||
**Status:** Accepted
|
||||
|
||||
## Context
|
||||
|
||||
Legacy used monolithic RenderStage that coupled fetching, rendering, and display. Hard to extend or test.
|
||||
|
||||
## Decision
|
||||
|
||||
Replace with composable Stage classes, each with single responsibility:
|
||||
|
||||
- **DataSourceStage**: Wraps DataSource, provides raw items
|
||||
- **SourceItemsToBufferStage**: Converts items to display buffer
|
||||
- **EffectPluginStage**: Applies effects chain
|
||||
- **DisplayStage**: Renders to display backend
|
||||
- **ViewportFilterStage**: Filters to visible region
|
||||
|
||||
Each Stage implements:
|
||||
- `capabilities`: What it provides
|
||||
- `dependencies`: What it needs
|
||||
- `process(buf, ctx)`: Transform the buffer
|
||||
|
||||
## Consequences
|
||||
|
||||
- **Positive:** Single responsibility - easier to test and debug
|
||||
- **Positive:** Replace any stage without affecting others
|
||||
- **Positive:** Pipeline introspection shows clear data flow
|
||||
- **Negative:** More classes to maintain
|
||||
|
||||
## References
|
||||
|
||||
- `engine/pipeline/core.py`: Stage ABC
|
||||
- `engine/pipeline/adapters.py`: Concrete stage implementations
|
||||
40
ADR-003-DataSource-Abstraction.md
Normal file
40
ADR-003-DataSource-Abstraction.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# ADR-003: DataSource Abstraction
|
||||
|
||||
**Date:** March 2026
|
||||
**Status:** Accepted
|
||||
|
||||
## Context
|
||||
|
||||
Headlines, poetry, and pipeline data all had different interfaces. Pipeline stages needed consistent access.
|
||||
|
||||
## Decision
|
||||
|
||||
Abstract DataSource class with get_items():
|
||||
|
||||
```python
|
||||
class DataSource(ABC):
|
||||
@abstractmethod
|
||||
def get_items(self) -> list[SourceItem]:
|
||||
...
|
||||
|
||||
class SourceRegistry:
|
||||
def get(self, name: str) -> DataSource: ...
|
||||
def list_available(self) -> list[str]: ...
|
||||
```
|
||||
|
||||
Implementations:
|
||||
- **HeadlinesDataSource**: Static RSS feed, cached
|
||||
- **PoetryDataSource**: Static poetry feed, cached
|
||||
- **ListDataSource**: Wraps pre-fetched items (testing)
|
||||
- **PipelineDataSource**: Dynamic, re-fetches each cycle
|
||||
|
||||
## Consequences
|
||||
|
||||
- **Positive:** Consistent interface across all source types
|
||||
- **Positive:** SourceRegistry enables discovery
|
||||
- **Negative:** Runtime overhead for dynamic sources
|
||||
|
||||
## References
|
||||
|
||||
- `engine/data_sources/sources.py`: DataSource, SourceRegistry
|
||||
- `tests/test_data_sources.py`: Comprehensive tests
|
||||
41
ADR-004-Display-Protocol-Pattern.md
Normal file
41
ADR-004-Display-Protocol-Pattern.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# ADR-004: Display Protocol Pattern
|
||||
|
||||
**Date:** March 2026
|
||||
**Status:** Accepted
|
||||
|
||||
## Context
|
||||
|
||||
Different display targets (terminal, browser, pygame) needed unified interface. Hard to add new backends.
|
||||
|
||||
## Decision
|
||||
|
||||
Display protocol with common interface:
|
||||
|
||||
```python
|
||||
class Display(Protocol):
|
||||
def setup(self, width: int, height: int) -> None: ...
|
||||
def draw(self, buf: Buffer) -> None: ...
|
||||
def refresh(self) -> None: ...
|
||||
def cleanup(self) -> None: ...
|
||||
```
|
||||
|
||||
DisplayRegistry auto-discovers backends from `engine/display/backends/`:
|
||||
|
||||
- **terminal.py**: ANSI escape sequences
|
||||
- **websocket.py**: HTML5 Canvas broadcast
|
||||
- **sixel.py**: Sixel graphics
|
||||
- **kitty.py**: Kitty protocol
|
||||
- **pygame.py**: Pygame window
|
||||
- **null.py**: Headless (testing)
|
||||
- **multi.py**: Forward to multiple backends
|
||||
|
||||
## Consequences
|
||||
|
||||
- **Positive:** Add new backend by implementing protocol
|
||||
- **Positive:** MultiDisplay enables simultaneous outputs
|
||||
- **Positive:** NullDisplay enables headless testing
|
||||
|
||||
## References
|
||||
|
||||
- `engine/display/__init__.py`: Display, DisplayRegistry
|
||||
- `engine/display/backends/`: Backend implementations
|
||||
56
ADR-005-Sensor-Framework.md
Normal file
56
ADR-005-Sensor-Framework.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# ADR-005: Sensor Framework
|
||||
|
||||
**Date:** March 2026
|
||||
**Status:** Accepted
|
||||
|
||||
## Context
|
||||
|
||||
Effects should react to real-time input (microphone, pipeline metrics) without hardcoding sensor logic.
|
||||
|
||||
## Decision
|
||||
|
||||
Sensor ABC with read() returning SensorValue:
|
||||
|
||||
```python
|
||||
class Sensor(ABC):
|
||||
name: str
|
||||
unit: str = ""
|
||||
|
||||
@abstractmethod
|
||||
def read(self) -> SensorValue | None: ...
|
||||
|
||||
@dataclass
|
||||
class SensorValue:
|
||||
sensor_name: str
|
||||
value: float
|
||||
timestamp: float
|
||||
unit: str = ""
|
||||
```
|
||||
|
||||
Implementations:
|
||||
- **MicSensor**: Microphone RMS dB (via sounddevice)
|
||||
- **PipelineMetricsSensor**: FPS, frame-time
|
||||
- **OscillatorSensor**: Test sine wave generator
|
||||
|
||||
SensorStage provides values to effects. Effects declare param_bindings:
|
||||
|
||||
```python
|
||||
class GlitchEffect(EffectPlugin):
|
||||
param_bindings = {
|
||||
"intensity": {"sensor": "mic", "transform": "linear"},
|
||||
}
|
||||
```
|
||||
|
||||
Transforms: linear, exponential, threshold
|
||||
|
||||
## Consequences
|
||||
|
||||
- **Positive:** Extensible - add new sensors without touching effects
|
||||
- **Positive:** Effects react to any sensor uniformly
|
||||
- **Negative:** Audio sensors require additional dependencies
|
||||
|
||||
## References
|
||||
|
||||
- `engine/sensors/__init__.py`: Sensor ABC, SensorRegistry
|
||||
- `engine/sensors/mic.py`: MicSensor
|
||||
- `engine/pipeline/adapters.py`: SensorStage
|
||||
50
ADR-006-Preset-TOML-Format.md
Normal file
50
ADR-006-Preset-TOML-Format.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# ADR-006: Preset TOML Format
|
||||
|
||||
**Date:** March 2026
|
||||
**Status:** Accepted
|
||||
|
||||
## Context
|
||||
|
||||
Pipeline presets needed configuration format. Avoid external dependencies (no YAML, JSON, etc.).
|
||||
|
||||
## Decision
|
||||
|
||||
TOML format with validation:
|
||||
|
||||
```toml
|
||||
[preset]
|
||||
name = "firehose"
|
||||
display = "terminal"
|
||||
|
||||
[[stages]]
|
||||
type = "datasource"
|
||||
source = "headlines"
|
||||
|
||||
[[stages]]
|
||||
type = "effect"
|
||||
name = "glitch"
|
||||
intensity = 0.5
|
||||
|
||||
[stages.config]
|
||||
param_bindings = { intensity = { sensor = "mic", transform = "linear" } }
|
||||
```
|
||||
|
||||
Locations (in order of priority):
|
||||
1. `./presets.toml` (local override)
|
||||
2. `~/.config/mainline/presets.toml` (user config)
|
||||
3. `engine/presets.toml` (built-in)
|
||||
|
||||
PresetLoader validates structure and signal paths (detects circular deps).
|
||||
|
||||
## Consequences
|
||||
|
||||
- **Positive:** No external dependencies (TOML built into Python 3.11+)
|
||||
- **Positive:** Human-readable, easy to edit
|
||||
- **Positive:** Validation prevents runtime errors
|
||||
- **Negative:** Must document TOML schema
|
||||
|
||||
## References
|
||||
|
||||
- `engine/pipeline/preset_loader.py`: Loading and validation
|
||||
- `engine/pipeline/presets.py`: Preset dataclasses
|
||||
- `engine/presets.toml`: Built-in presets
|
||||
Reference in New Issue
Block a user