Own every pixel each frame, and bring the cursor back as a blinking block
Some checks failed
akbasic CI Build / cmake_build (push) Failing after 19s
akbasic CI Build / sanitizers (push) Failing after 13s
akbasic CI Build / coverage (push) Failing after 16s
akbasic CI Build / akgl_build (push) Failing after 18s
akbasic CI Build / mutation_test (push) Failing after 17s

The blinking backspace was not the backspace. The sink skipped rows it
believed were already clear, tracked in a drawn[] array, which assumes a frame
inherits the frame before it. SDL_RenderPresent swaps buffers, so a frame
inherits the one two back: a row erased once is clean in one buffer and dirty
in the other, and presenting alternates between them. The first character of a
backspaced wrap flickered forever.

It reproduces on X11 and never under the dummy driver or a software renderer,
which is why the suite stayed green through two rounds of fixing it. The sink
now repaints every row it owns every frame -- a frame either owns every pixel
it presents or inherits pixels it cannot reason about.

Confirmed on real hardware with the reported input: the wrapped tail's row
reads zero across five successive captures.

The cursor returns as a blinking block, half a second per cycle, off SDL's
clock. It sits in the cell after the text rather than under it, which is what
made the underscore unreadable, and it wraps to the next row when a line
exactly fills one -- where the next character actually lands.

The new regression test paints stale pixels by hand, which is what a swapped-in
buffer hands back, so the case is covered without real hardware. Every new
assertion was checked by reverting the fix and watching it fail.

Recorded honestly in TODO.md section 5: the same buffer swap means the
graphics verbs were never reliable across frames either. A one-shot DRAW lands
in one buffer and the next present shows the other. Making that work needs a
persistent surface, which is its own commit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-31 15:23:41 -04:00
parent acd20eed9a
commit df50201121
4 changed files with 222 additions and 74 deletions

View File

@@ -52,6 +52,14 @@
#include <akbasic/input.h>
#include <akbasic/sink.h>
/**
* @brief One full cursor blink in milliseconds, half on and half off.
*
* Half a second, which is about what a Commodore does and is slow enough to read
* under.
*/
#define AKBASIC_SINK_CURSOR_BLINK_MS 500
/** @brief How many saved SSHAPE regions the graphics backend will hold at once. */
#define AKBASIC_AKGL_MAX_SHAPES 16
@@ -113,12 +121,10 @@ typedef struct
char text[64][256];
/**
* Which rows carried glyphs when this sink last rendered. A row that has
* emptied since -- scrolled away, cleared, or shortened -- still has to be
* erased, and after it is erased there is no longer anything in the grid to
* say it once had something in it.
* Milliseconds for one full cursor blink, half on and half off. Zero holds
* the cursor solid, which is what a test wanting a deterministic frame sets.
*/
bool drawn[64];
uint64_t cursorperiodms;
/*
* The line editor. Nothing here is live except while readline is running,