feat(positioning): Add configurable PositionStage for positioning modes

- Added PositioningMode enum (ABSOLUTE, RELATIVE, MIXED)
- Created PositionStage class with configurable positioning modes
- Updated terminal display to support positioning parameter
- Updated PipelineParams to include positioning field
- Updated DisplayStage to pass positioning to terminal display
- Added documentation in docs/positioning-analysis.md

Positioning modes:
- ABSOLUTE: Each line has cursor positioning codes (\033[row;1H)
- RELATIVE: Lines use newlines (no cursor codes, better for scrolling)
- MIXED: Base content uses newlines, effects use absolute positioning (default)

Usage:
  # In pipeline or preset:
  positioning = "absolute"  # or "relative" or "mixed"

  # Via command line (future):
  --positioning absolute
This commit is contained in:
2026-03-21 17:38:20 -07:00
parent 5352054d09
commit 33df254409
7 changed files with 556 additions and 6 deletions

View File

@@ -83,7 +83,16 @@ class TerminalDisplay:
return self._cached_dimensions
def show(self, buffer: list[str], border: bool = False) -> None:
def show(
self, buffer: list[str], border: bool = False, positioning: str = "mixed"
) -> None:
"""Display buffer with optional border and positioning mode.
Args:
buffer: List of lines to display
border: Whether to apply border
positioning: Positioning mode - "mixed" (default), "absolute", or "relative"
"""
import sys
from engine.display import get_monitor, render_border
@@ -109,8 +118,27 @@ class TerminalDisplay:
if border and border != BorderMode.OFF:
buffer = render_border(buffer, self.width, self.height, fps, frame_time)
# Write buffer with cursor home + erase down to avoid flicker
output = "\033[H\033[J" + "\n".join(buffer)
# Apply positioning based on mode
if positioning == "absolute":
# All lines should have cursor positioning codes
# Join with newlines (cursor codes already in buffer)
output = "\033[H\033[J" + "\n".join(buffer)
elif positioning == "relative":
# Remove cursor positioning codes (except colors) and join with newlines
import re
cleaned_buffer = []
for line in buffer:
# Remove cursor positioning codes but keep color codes
# Pattern: \033[row;colH or \033[row;col;...H
cleaned = re.sub(r"\033\[[0-9;]*H", "", line)
cleaned_buffer.append(cleaned)
output = "\033[H\033[J" + "\n".join(cleaned_buffer)
else: # mixed (default)
# Current behavior: join with newlines
# Effects that need absolute positioning have their own cursor codes
output = "\033[H\033[J" + "\n".join(buffer)
sys.stdout.buffer.write(output.encode())
sys.stdout.flush()