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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
Anachronaut
2026-08-30 17:08:27 -04:00
co-authored by Claude Opus 5
parent 553882d28d
commit 0e0731e2b1
10 changed files with 51 additions and 9 deletions
+17 -1
View File
@@ -1071,7 +1071,23 @@ uint8_t OutputHandler(uint8_t DataByte, uint8_t Address) {
case CONSOLE_COMMAND: case CONSOLE_COMMAND:
if (DataByte == CONSOLE_COMMAND_CLEAR) { if (DataByte == CONSOLE_COMMAND_CLEAR) {
consoleClearScreen(0); 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 // Anything else does nothing. A command block reserved for later should be
// quiet rather than fatal, the same as the screen's spare registers. // quiet rather than fatal, the same as the screen's spare registers.
+1 -1
View File
@@ -694,7 +694,7 @@ Three more registers, because that is how this machine talks to everything else.
| --- | --- | | --- | --- |
| 0x03 | Cursor row. Read and write. | | 0x03 | Cursor row. Read and write. |
| 0x04 | Cursor column. 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. | | 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. **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.
+1 -1
View File
@@ -1,4 +1,4 @@
 #  #
# #
### ###
+1 -1
View File
@@ -1,4 +1,4 @@
 #  #
# #
### ###
+1 -1
View File
@@ -1,6 +1,6 @@
CosmOS CosmOS
> loaded, starting at 4000 > loaded, starting at 4000
>  # >  #
# #
### ###
+1 -1
View File
@@ -1,6 +1,6 @@
CosmOS CosmOS
> loaded, starting at 4000 > loaded, starting at 4000
>  # >  #
# #
### ###
+1 -1
View File
@@ -1,6 +1,6 @@
CosmOS CosmOS
> loaded, starting at 4000 > loaded, starting at 4000
> +----------------+ > +----------------+
| | | |
| | | |
| | | |
+1 -1
View File
@@ -4,7 +4,7 @@ CosmOS ready.
> echo and loud again > echo and loud again
and loud again and loud again
and quiet again at the end and quiet again at the end
> > typed by hand > > typed by hand
> halted > halted
Execution halted. Execution halted.
[exit 0] [exit 0]
+1 -1
View File
@@ -1,6 +1,6 @@
CosmOS CosmOS
startup.sh is there but does not begin with #!, so it was not run startup.sh is there but does not begin with #!, so it was not run
> > typed by hand > > typed by hand
> halted > halted
Execution halted. Execution halted.
[exit 0] [exit 0]
+26
View File
@@ -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 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" || 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. # And what scrolled off the top is still in the map, which is scrollback nothing had to keep.
{ printf '#Program\nstart:\n' { printf '#Program\nstart:\n'
say "A" say "A"