feat(core): add Camera abstraction for viewport scrolling

- Add Camera class with modes: vertical, horizontal, omni, floating
- Refactor scroll.py and demo to use Camera abstraction
- Add vis_offset for horizontal scrolling support
- Add camera_x to EffectContext for effects
- Add pygame window resize handling
- Add HUD effect plugin for demo mode
- Add --demo flag to run demo mode
- Add tests for Camera and vis_offset
This commit is contained in:
2026-03-16 01:46:21 -07:00
parent e1408dcf16
commit 9b139a40f7
10 changed files with 343 additions and 37 deletions

View File

@@ -352,10 +352,11 @@ def pick_effects_config():
def run_demo_mode():
"""Run demo mode - showcases effects with real content and pygame display."""
"""Run demo mode - showcases effects and camera modes with real content."""
import random
from engine import config
from engine.camera import Camera, CameraMode
from engine.display import DisplayRegistry
from engine.effects import (
EffectContext,
@@ -409,7 +410,6 @@ def run_demo_mode():
pool = list(items)
seen = set()
active = []
scroll_cam = 0
ticker_next_y = 0
noise_cache = {}
scroll_motion_accum = 0.0
@@ -418,6 +418,8 @@ def run_demo_mode():
GAP = 3
scroll_step_interval = calculate_scroll_step(config.SCROLL_DUR, h)
camera = Camera.vertical(speed=1.0)
effects_to_demo = ["noise", "fade", "glitch", "firehose"]
effect_idx = 0
effect_name = effects_to_demo[effect_idx]
@@ -425,12 +427,22 @@ def run_demo_mode():
current_intensity = 0.0
ramping_up = True
print(" \033[38;5;82mStarting effect demo...\033[0m")
camera_modes = [
(CameraMode.VERTICAL, "vertical"),
(CameraMode.HORIZONTAL, "horizontal"),
(CameraMode.OMNI, "omni"),
(CameraMode.FLOATING, "floating"),
]
camera_mode_idx = 0
camera_start_time = time.time()
print(" \033[38;5;82mStarting effect & camera demo...\033[0m")
print(" \033[38;5;245mPress Ctrl+C to exit\033[0m\n")
try:
while True:
elapsed = time.time() - effect_start_time
camera_elapsed = time.time() - camera_start_time
duration = config.DEMO_EFFECT_DURATION
if elapsed >= duration:
@@ -441,6 +453,13 @@ def run_demo_mode():
current_intensity = 0.0
ramping_up = True
if camera_elapsed >= duration * 2:
camera_mode_idx = (camera_mode_idx + 1) % len(camera_modes)
mode, mode_name = camera_modes[camera_mode_idx]
camera = Camera(mode=mode, speed=1.0)
camera_start_time = time.time()
camera_elapsed = 0
progress = elapsed / duration
if ramping_up:
current_intensity = progress
@@ -458,18 +477,21 @@ def run_demo_mode():
hud_effect = registry.get("hud")
if hud_effect:
hud_effect.config.params["display_effect"] = effect_name
mode_name = camera_modes[camera_mode_idx][1]
hud_effect.config.params["display_effect"] = (
f"{effect_name} / {mode_name}"
)
hud_effect.config.params["display_intensity"] = current_intensity
scroll_motion_accum += config.FRAME_DT
while scroll_motion_accum >= scroll_step_interval:
scroll_motion_accum -= scroll_step_interval
scroll_cam += 1
camera.update(config.FRAME_DT)
from engine.effects import next_headline
from engine.render import make_block
while ticker_next_y < camera.y + h + 10 and len(active) < 50:
from engine.effects import next_headline
from engine.render import make_block
while ticker_next_y < scroll_cam + h + 10:
t, src, ts = next_headline(pool, items, seen)
ticker_content, hc, midx = make_block(t, src, ts, w)
active.append((ticker_content, hc, ticker_next_y, midx))
@@ -478,10 +500,10 @@ def run_demo_mode():
active = [
(c, hc, by, mi)
for c, hc, by, mi in active
if by + len(c) > scroll_cam
if by + len(c) > camera.y
]
for k in list(noise_cache):
if k < scroll_cam:
if k < camera.y:
del noise_cache[k]
grad_offset = (time.time() * config.GRAD_SPEED) % 1.0
@@ -489,7 +511,13 @@ def run_demo_mode():
from engine.layers import render_ticker_zone
buf, noise_cache = render_ticker_zone(
active, scroll_cam, h, w, noise_cache, grad_offset
active,
scroll_cam=camera.y,
camera_x=camera.x,
ticker_h=h,
w=w,
noise_cache=noise_cache,
grad_offset=grad_offset,
)
from engine.layers import render_firehose
@@ -500,8 +528,9 @@ def run_demo_mode():
ctx = EffectContext(
terminal_width=w,
terminal_height=h,
scroll_cam=scroll_cam,
scroll_cam=camera.y,
ticker_height=h,
camera_x=camera.x,
mic_excess=0.0,
grad_offset=grad_offset,
frame_number=frame_number,