feat: Implement scrolling camera with layout-aware filtering
- Rename VERTICAL camera mode to FEED (rapid single-item view) - Add SCROLL camera mode with float accumulation for smooth movie-credits style scrolling - Add estimate_block_height() for cheap layout calculation without full rendering - Replace ViewportFilterStage with layout-aware filtering that tracks camera position - Add render caching to FontStage to avoid re-rendering items - Fix CameraStage to use global canvas height for scrolling bounds - Add horizontal padding in Camera.apply() to prevent ghosting - Add get_dimensions() to MultiDisplay for proper viewport sizing - Fix PygameDisplay to auto-detect viewport from window size - Update presets to use scroll camera with appropriate speeds
This commit is contained in:
@@ -1,19 +1,18 @@
|
||||
|
||||
from engine.camera import Camera, CameraMode
|
||||
|
||||
|
||||
def test_camera_vertical_default():
|
||||
"""Test default vertical camera."""
|
||||
cam = Camera()
|
||||
assert cam.mode == CameraMode.VERTICAL
|
||||
assert cam.mode == CameraMode.FEED
|
||||
assert cam.x == 0
|
||||
assert cam.y == 0
|
||||
|
||||
|
||||
def test_camera_vertical_factory():
|
||||
"""Test vertical factory method."""
|
||||
cam = Camera.vertical(speed=2.0)
|
||||
assert cam.mode == CameraMode.VERTICAL
|
||||
cam = Camera.feed(speed=2.0)
|
||||
assert cam.mode == CameraMode.FEED
|
||||
assert cam.speed == 2.0
|
||||
|
||||
|
||||
|
||||
@@ -75,8 +75,8 @@ class TestViewportFilterPerformance:
|
||||
|
||||
With 1438 items and 24-line viewport:
|
||||
- Without filter: FontStage renders all 1438 items
|
||||
- With filter: FontStage renders ~5 items
|
||||
- Expected improvement: 1438 / 5 ≈ 288x
|
||||
- With filter: FontStage renders ~3 items (layout-based)
|
||||
- Expected improvement: 1438 / 3 ≈ 479x
|
||||
"""
|
||||
test_items = [
|
||||
SourceItem(f"Headline {i}", "source", str(i)) for i in range(1438)
|
||||
@@ -89,10 +89,10 @@ class TestViewportFilterPerformance:
|
||||
filtered = stage.process(test_items, ctx)
|
||||
improvement_factor = len(test_items) / len(filtered)
|
||||
|
||||
# Verify we get expected 288x improvement
|
||||
assert 250 < improvement_factor < 300
|
||||
# Verify filtered count is reasonable
|
||||
assert 4 <= len(filtered) <= 6
|
||||
# Verify we get expected ~479x improvement (better than old ~288x)
|
||||
assert 400 < improvement_factor < 600
|
||||
# Verify filtered count is reasonable (layout-based is more precise)
|
||||
assert 2 <= len(filtered) <= 5
|
||||
|
||||
|
||||
class TestPipelinePerformanceWithRealData:
|
||||
|
||||
@@ -627,12 +627,12 @@ class TestStageAdapters:
|
||||
from engine.pipeline.adapters import CameraStage
|
||||
from engine.pipeline.core import PipelineContext
|
||||
|
||||
camera = Camera(mode=CameraMode.VERTICAL)
|
||||
camera = Camera(mode=CameraMode.FEED)
|
||||
stage = CameraStage(camera, name="vertical")
|
||||
PipelineContext()
|
||||
|
||||
assert "camera" in stage.capabilities
|
||||
assert "source" in stage.dependencies # Prefix matches any source
|
||||
assert "render.output" in stage.dependencies # Depends on rendered content
|
||||
|
||||
|
||||
class TestDataSourceStage:
|
||||
|
||||
@@ -5,7 +5,6 @@ of items processed by FontStage, preventing the 10+ second hangs observed with
|
||||
large headline sources.
|
||||
"""
|
||||
|
||||
|
||||
from engine.data_sources.sources import SourceItem
|
||||
from engine.pipeline.adapters import ViewportFilterStage
|
||||
from engine.pipeline.core import PipelineContext
|
||||
@@ -97,7 +96,8 @@ class TestViewportFilterStage:
|
||||
# With 1438 items and 24-line viewport:
|
||||
# - Without filter: FontStage renders all 1438 items
|
||||
# - With filter: FontStage renders only ~5 items
|
||||
# - Improvement: 1438 / 5 = 287.6x fewer items to render
|
||||
# - Improvement: 1438 / 3 = ~479x fewer items to render
|
||||
# (layout-based filtering is more precise than old estimate)
|
||||
|
||||
test_items = [
|
||||
SourceItem(f"Headline {i}", "source", str(i)) for i in range(1438)
|
||||
@@ -110,10 +110,10 @@ class TestViewportFilterStage:
|
||||
filtered = stage.process(test_items, ctx)
|
||||
improvement_factor = len(test_items) / len(filtered)
|
||||
|
||||
# Verify we get at least 200x improvement
|
||||
assert improvement_factor > 200
|
||||
# Verify we get the expected ~288x improvement
|
||||
assert 250 < improvement_factor < 300
|
||||
# Verify we get at least 400x improvement (better than old ~288x)
|
||||
assert improvement_factor > 400
|
||||
# Verify we get the expected ~479x improvement
|
||||
assert 400 < improvement_factor < 600
|
||||
|
||||
|
||||
class TestViewportFilterIntegration:
|
||||
|
||||
Reference in New Issue
Block a user