From 0e0731e2b1d955f1849921a56df6bf3eb6081f12 Mon Sep 17 00:00:00 2001 From: Anachronaut Date: Sun, 30 Aug 2026 17:08:27 -0400 Subject: [PATCH] Put the cursor home when the screen is cleared A screen with nothing on it and a cursor half way down it is not a cleared screen: the next thing written lands where the last thing happened to leave off, at a position whose meaning was just erased. The shell's clear did exactly that, and left the next line one row below wherever it had been. Both halves were missing. consoleClearScreen blanks the cells and does not touch cursorRow or cursorColumn, and 2J on a terminal empties the screen without moving anything - H is what puts the cursor at the top. Life and Snake never showed this because they follow their clear with an explicit 1;1H of their own. They were working around it, which is why the bug survived until a command cleared the screen and then let somebody type. The attribute is deliberately not reset. Clearing is about what is on the screen rather than how the next thing will be drawn, and a program that chose a colour and then cleared still wants that colour - which is what a terminal does too. Two checks in video.sh, and they took two goes to make independent. The first pointed at the row where the letter lands when the cursor is NOT homed, so removing the clear and removing the homing failed the same pair and neither said which. The second now looks at a row nothing writes to either way - and at a letter whose ink actually reaches the pixel it reads, which "two" did not, so it passed on a screen that had never been cleared. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW --- Source/Emulator/io.c | 18 +++++++++++++++++- SplitBit Programming Manual.md | 2 +- Tests/expected/16x16Life.out | 2 +- Tests/expected/16x16LifeModern.out | 2 +- Tests/expected/cosmosLife.out | 2 +- Tests/expected/cosmosLifeKey.out | 2 +- Tests/expected/cosmosSnake.out | 2 +- Tests/expected/cosmosStartup.out | 2 +- Tests/expected/cosmosStartupBad.out | 2 +- Tests/video.sh | 26 ++++++++++++++++++++++++++ 10 files changed, 51 insertions(+), 9 deletions(-) diff --git a/Source/Emulator/io.c b/Source/Emulator/io.c index 7160acc..19e0d7f 100644 --- a/Source/Emulator/io.c +++ b/Source/Emulator/io.c @@ -1071,7 +1071,23 @@ uint8_t OutputHandler(uint8_t DataByte, uint8_t Address) { case CONSOLE_COMMAND: if (DataByte == CONSOLE_COMMAND_CLEAR) { consoleClearScreen(0); - consoleTellTerminal("\033[2J"); + // ---- And the cursor goes home ---- + // + // A screen with nothing on it and a cursor half way down it is not a cleared + // screen: the next thing written lands where the last thing happened to + // leave off, which is a position that no longer means anything because + // whatever gave it meaning has just been erased. + // + // The attribute is deliberately NOT reset. Clearing is about what is on the + // screen and not about how the next thing will be drawn - a program that + // chose a colour and then cleared still wants that colour, the same as on a + // terminal, where 2J does not touch the graphics state either. + cursorRow = 0; + cursorColumn = 0; + consoleCursorMoved(); + // 2J empties it and H is what puts the cursor at the top. A terminal given + // only the first does exactly what this device did before this comment. + consoleTellTerminal("\033[2J\033[H"); } // Anything else does nothing. A command block reserved for later should be // quiet rather than fatal, the same as the screen's spare registers. diff --git a/SplitBit Programming Manual.md b/SplitBit Programming Manual.md index 9d27b81..b6f4733 100644 --- a/SplitBit Programming Manual.md +++ b/SplitBit Programming Manual.md @@ -694,7 +694,7 @@ Three more registers, because that is how this machine talks to everything else. | --- | --- | | 0x03 | Cursor row. Read and write. | | 0x04 | Cursor column. Read and write. | -| 0x05 | Command. Write 1 to clear the screen. | +| 0x05 | Command. Write 1 to clear the screen, which also puts the cursor at the top left. | | 0x06 | Attribute. Read and write. | **A cursor is shown only when it is asked for**, with bit 2 of the Control port, and status bit 4 says whether one is being shown. Off is the right default for a machine: a program painting its own screen does not want something blinking in the middle of it, and a system that reads lines from a person turns it on. diff --git a/Tests/expected/16x16Life.out b/Tests/expected/16x16Life.out index 53b44fe..32aca3c 100644 --- a/Tests/expected/16x16Life.out +++ b/Tests/expected/16x16Life.out @@ -1,4 +1,4 @@ - # + # # ### diff --git a/Tests/expected/16x16LifeModern.out b/Tests/expected/16x16LifeModern.out index b35b407..033e3fd 100644 --- a/Tests/expected/16x16LifeModern.out +++ b/Tests/expected/16x16LifeModern.out @@ -1,4 +1,4 @@ - # + # # ### diff --git a/Tests/expected/cosmosLife.out b/Tests/expected/cosmosLife.out index 5518d4e..bfd2203 100644 --- a/Tests/expected/cosmosLife.out +++ b/Tests/expected/cosmosLife.out @@ -1,6 +1,6 @@ CosmOS > loaded, starting at 4000 ->  # +>  # # ### diff --git a/Tests/expected/cosmosLifeKey.out b/Tests/expected/cosmosLifeKey.out index 34aebcb..8206a60 100644 --- a/Tests/expected/cosmosLifeKey.out +++ b/Tests/expected/cosmosLifeKey.out @@ -1,6 +1,6 @@ CosmOS > loaded, starting at 4000 ->  # +>  # # ### diff --git a/Tests/expected/cosmosSnake.out b/Tests/expected/cosmosSnake.out index 732a945..20c1cd8 100644 --- a/Tests/expected/cosmosSnake.out +++ b/Tests/expected/cosmosSnake.out @@ -1,6 +1,6 @@ CosmOS > loaded, starting at 4000 -> +----------------+ +> +----------------+ | | | | | | diff --git a/Tests/expected/cosmosStartup.out b/Tests/expected/cosmosStartup.out index d811bee..8240048 100644 --- a/Tests/expected/cosmosStartup.out +++ b/Tests/expected/cosmosStartup.out @@ -4,7 +4,7 @@ CosmOS ready. > echo and loud again and loud again and quiet again at the end -> > typed by hand +> > typed by hand > halted Execution halted. [exit 0] diff --git a/Tests/expected/cosmosStartupBad.out b/Tests/expected/cosmosStartupBad.out index 0823e92..65f6819 100644 --- a/Tests/expected/cosmosStartupBad.out +++ b/Tests/expected/cosmosStartupBad.out @@ -1,6 +1,6 @@ CosmOS startup.sh is there but does not begin with #!, so it was not run -> > typed by hand +> > typed by hand > halted Execution halted. [exit 0] diff --git a/Tests/video.sh b/Tests/video.sh index e2ebe7d..67c8384 100755 --- a/Tests/video.sh +++ b/Tests/video.sh @@ -718,6 +718,32 @@ TOTAL="$(grep -oE 'after [0-9]+' "$BUILD/poller.out" | grep -oE '[0-9]+')" && result ok "and the flag comes down when looked at" "$TOTAL cycles, so three frames passed" \ || result no "and the flag comes down when looked at" "$TOTAL cycles, too few to be three frames" +# ---- Clearing puts the cursor back at the top ---- +# +# A screen with nothing on it and a cursor half way down it is not a cleared screen. This +# writes three lines, clears, and writes one letter: it has to land in the very first cell. +# Before the cursor was homed it landed on the fourth row, on a screen that no longer had +# anything on the first three to justify it. +{ printf '#Program\nstart:\n' + say "AAA"; emit 10 + say "AAA"; emit 10 + say "AAA"; emit 10 + port 0x05 0x01 + say "X" + epilogue +} | run clearhome || exit 1 +inked clearhome 2 1 \ + && result ok "clearing puts the cursor home" "the next letter landed in the first cell" \ + || result no "clearing puts the cursor home" "nothing at 2,1" +# And the three lines really are gone, so the check above is about the cursor rather than +# about a clear that did nothing. +# The SECOND row, which nothing writes to either way - so this fails when the clear did not +# clear and passes whether or not the cursor was homed. Pointed at the fourth row it failed +# for the same reason as the check above, which is a second check that says nothing. +papered clearhome 2 9 \ + && result ok "and the screen really was cleared" "the second row is empty" \ + || result no "and the screen really was cleared" "there is still ink on the second row" + # And what scrolled off the top is still in the map, which is scrollback nothing had to keep. { printf '#Program\nstart:\n' say "A"