forked from genewildish/Mainline
feat: Complete pipeline hot-rebuild implementation with acceptance tests
- Implements pipeline hot-rebuild with state preservation (issue #43) - Adds auto-injection of MVP stages for missing capabilities - Adds radial camera mode for polar coordinate scanning - Adds afterimage and motionblur effects using framebuffer history - Adds comprehensive acceptance tests for camera modes and pipeline rebuild - Updates presets.toml with new effect configurations Related to: #35 (Pipeline Mutation API epic) Closes: #43, #44, #45
This commit is contained in:
@@ -49,7 +49,9 @@ class ViewportFilterStage(Stage):
|
||||
|
||||
@property
|
||||
def dependencies(self) -> set[str]:
|
||||
return {"source"}
|
||||
# Always requires camera.state for viewport filtering
|
||||
# CameraUpdateStage provides this (auto-injected if missing)
|
||||
return {"source", "camera.state"}
|
||||
|
||||
@property
|
||||
def inlet_types(self) -> set:
|
||||
@@ -95,9 +97,13 @@ class ViewportFilterStage(Stage):
|
||||
|
||||
# Find start index (first item that intersects with visible range)
|
||||
start_idx = 0
|
||||
start_item_y = 0 # Y position where the first visible item starts
|
||||
for i, total_h in enumerate(cumulative_heights):
|
||||
if total_h > start_y:
|
||||
start_idx = i
|
||||
# Calculate the Y position of the start of this item
|
||||
if i > 0:
|
||||
start_item_y = cumulative_heights[i - 1]
|
||||
break
|
||||
|
||||
# Find end index (first item that extends beyond visible range)
|
||||
@@ -107,6 +113,16 @@ class ViewportFilterStage(Stage):
|
||||
end_idx = i + 1
|
||||
break
|
||||
|
||||
# Adjust camera_y for the filtered buffer
|
||||
# The filtered buffer starts at row 0, but the camera position
|
||||
# needs to be relative to where the first visible item starts
|
||||
filtered_camera_y = camera_y - start_item_y
|
||||
|
||||
# Update context with the filtered camera position
|
||||
# This ensures CameraStage can correctly slice the filtered buffer
|
||||
ctx.set_state("camera_y", filtered_camera_y)
|
||||
ctx.set_state("camera_x", ctx.get("camera_x", 0)) # Keep camera_x unchanged
|
||||
|
||||
# Return visible items
|
||||
return data[start_idx:end_idx]
|
||||
|
||||
@@ -127,9 +143,16 @@ class FontStage(Stage):
|
||||
def capabilities(self) -> set[str]:
|
||||
return {"render.output"}
|
||||
|
||||
@property
|
||||
def stage_dependencies(self) -> set[str]:
|
||||
# Must connect to viewport_filter stage to get filtered source
|
||||
return {"viewport_filter"}
|
||||
|
||||
@property
|
||||
def dependencies(self) -> set[str]:
|
||||
return {"source"}
|
||||
# Depend on source.filtered (provided by viewport_filter)
|
||||
# This ensures we get the filtered/processed source, not raw source
|
||||
return {"source.filtered"}
|
||||
|
||||
@property
|
||||
def inlet_types(self) -> set:
|
||||
@@ -147,6 +170,11 @@ class FontStage(Stage):
|
||||
if not isinstance(data, list):
|
||||
return [str(data)]
|
||||
|
||||
import os
|
||||
|
||||
if os.environ.get("DEBUG_CAMERA"):
|
||||
print(f"FontStage: input items={len(data)}")
|
||||
|
||||
viewport_width = ctx.params.viewport_width if ctx.params else 80
|
||||
|
||||
result = []
|
||||
|
||||
Reference in New Issue
Block a user