From 2598218a4d69c574651c93d2a021b6bffd0fe4eb Mon Sep 17 00:00:00 2001 From: Hans Kokx Date: Fri, 20 Mar 2026 18:03:19 +0100 Subject: [PATCH] feat: Improve Sixel rendering stability by adjusting output height and anchoring behavior Signed-off-by: Hans Kokx --- apps/wolf_3d_cli/lib/cli_game_loop.dart | 15 ++++++++----- .../lib/src/rendering/sixel_renderer.dart | 22 +++++++++++++------ 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/apps/wolf_3d_cli/lib/cli_game_loop.dart b/apps/wolf_3d_cli/lib/cli_game_loop.dart index 5e2facc..2a6df2e 100644 --- a/apps/wolf_3d_cli/lib/cli_game_loop.dart +++ b/apps/wolf_3d_cli/lib/cli_game_loop.dart @@ -68,7 +68,8 @@ class CliGameLoop { } } - stdout.write('\x1b[?25l\x1b[2J'); + // Disable Sixel scrolling mode so frames overwrite in-place. + stdout.write('\x1b[?80l\x1b[?25l\x1b[2J'); _stdinSubscription = _stdinStream.listen(_handleInput); _stopwatch.start(); @@ -101,7 +102,8 @@ class CliGameLoop { } if (stdout.hasTerminal) { - stdout.write('\x1b[0m\x1b[?25h'); + // Restore scrolling Sixel mode and cursor visibility. + stdout.write('\x1b[0m\x1b[?80h\x1b[?25h'); } _isRunning = false; @@ -200,12 +202,15 @@ class CliGameLoop { return; } + final int safeCols = cols > 1 ? cols - 1 : cols; final String hint = _buildShortcutHintText(); - final String visible = hint.length > cols ? hint.substring(0, cols) : hint; - final String padded = visible.padRight(cols); + final String visible = hint.length > safeCols + ? hint.substring(0, safeCols) + : hint; + final String padded = visible.padRight(safeCols); // Draw an overlay line without disturbing the renderer's cursor position. - stdout.write('\x1b[s\x1b[1;1H\x1b[0m\x1b[2m$padded\x1b[0m\x1b[u'); + stdout.write('\x1b[s\x1b[1;1H\x1b[0m\x1b[2m\x1b[2K$padded\x1b[0m\x1b[u'); } String _buildShortcutHintText() { diff --git a/packages/wolf_3d_dart/lib/src/rendering/sixel_renderer.dart b/packages/wolf_3d_dart/lib/src/rendering/sixel_renderer.dart index fa40328..a33f0b5 100644 --- a/packages/wolf_3d_dart/lib/src/rendering/sixel_renderer.dart +++ b/packages/wolf_3d_dart/lib/src/rendering/sixel_renderer.dart @@ -200,14 +200,22 @@ class SixelRenderer extends CliRendererBackend { // Horizontal: cell-width estimates vary by terminal/font and can cause // right-shift clipping, so keep the image anchored at column 0. - // Vertical: keep one spare row to avoid scroll, but anchor to the bottom - // of the viewport so we avoid obvious empty space under the image. - final int imageRows = math.max( - 1, - (_outputHeight / _defaultLineHeightPx).ceil() + _terminalRowSafetyMargin, - ); + // Vertical: force top anchoring to keep repaint location stable across + // terminals that still scroll when rendering near the bottom edge. _offsetColumns = 0; - _offsetRows = math.max(0, terminalRows - imageRows); + _offsetRows = 0; + + // Clamp output height to a conservative terminal-row budget so Sixel data + // cannot extend beyond the viewport and trigger implicit scroll. + final int maxImageRows = math.max( + 1, + terminalRows - _terminalRowSafetyMargin, + ); + final int maxOutputHeight = maxImageRows * _defaultLineHeightPx; + if (_outputHeight > maxOutputHeight) { + _outputHeight = maxOutputHeight; + _outputWidth = math.max(1, (_outputHeight * _targetAspectRatio).floor()); + } if (_offsetColumns != previousOffsetColumns || _offsetRows != previousOffsetRows ||