Fix: Pending-Wrap-Kaskade in iTerm2 behoben

render() schrieb pro Nicht-Letzte-Zeile exakt self.cols Zeichen → Cursor
landet in Pending-Wrap-Zustand. In iTerm2 löscht ein Cursor-Positionierungs-
befehl (_go) diesen Zustand NICHT: das erste Zeichen der nächsten Zeile
triggert den Wrap → Zeile r+1 landet auf r+2 usw. → Kaskade.

Folgen: Zeilen ohne Überschreibung zeigen alte Zeichen (Fragmente oberhalb
der Wasserlinie), Unterwasserinhalt verschiebt sich aus dem sichtbaren
Bereich (nichts unter der Wasserlinie sichtbar).

Fix: letztes Zeichen jeder Zeile grundsätzlich auslassen (col_limit =
cols - 1, für alle Zeilen). Pending-Wrap tritt nie auf. Letzte Spalte
bleibt leer, was für die Animation völlig akzeptabel ist.
This commit is contained in:
rene 2026-03-29 15:34:31 +02:00
parent 66671eda8b
commit d87088eff0

View file

@ -119,18 +119,22 @@ class Canvas:
self._ck[row][col] = ck
def render(self) -> str:
# \033[H homes the cursor; every cell is rewritten below so \033[2J is
# not needed and would push content into the scrollback on every frame.
# The very last cell (bottom-right corner) is intentionally skipped:
# writing to it would trigger auto-wrap and scroll the terminal.
last_row = self.rows - 1
last_col = self.cols - 1
# \033[H homes the cursor; every cell is rewritten so \033[2J is not
# needed (it would push content into the scrollback on every frame).
#
# The last column of EVERY row is intentionally skipped (not just the
# last row). Writing to the last column triggers "pending-wrap" state.
# In iTerm2, cursor-position commands (_go) do NOT clear pending-wrap,
# so the first character of the next row triggers a wrap, placing it one
# row too low. This cascades: row r+1 lands at r+2, row r+2 at r+3, …
# Content below the waterline shifts off-screen; skipped rows show stale
# characters. Staying one column short prevents pending-wrap entirely.
col_limit = self.cols - 1
parts = ["\033[H", RESET]
sentinel = object()
last_ck: object = sentinel
for r in range(self.rows):
parts.append(_go(r, 0))
col_limit = last_col if r == last_row else self.cols
for c in range(col_limit):
ck = self._ck[r][c]
if ck is not last_ck: