Replace the escape parser with cursor registers
The console had grown an ANSI parser, and that was the wrong shape. ANSI exists because a screen used to be on the other end of a serial line and a byte stream was the only channel there was. This screen is memory the program can already address, so reaching it by sending characters for a state machine to take apart is a middleman for something the machine does better - and it meant accepting an open protocol somebody else defines, in hardware, with no natural end to it. Everything else on this machine is registers. So the console gets three: cursor row at 0x03, cursor column at 0x04, and a command port at 0x05 where 1 clears the screen. Both cursor registers are READ as well as written, which is the thing an escape cannot do without sending a query and parsing a reply - a routine that wants to put the cursor back where it found it can now ask. Clearing is one command against a thousand cells walked one at a time. Snake and Life are smaller for it: 2,168 bytes to 2,163 and 1,410 to 1,396. A HOST TERMINAL STILL SPEAKS ANSI, and bridging to the host is the emulator's job, the same job it does reading standard input. So the escapes are now GENERATED, outbound, for the set this device chooses, rather than parsed inbound as though the machine were a terminal. The set cannot grow behind our backs because we are the ones saying it. The cursor is announced lazily, at the next character rather than at the register write, so setting a row and a column costs one sequence rather than two. The console's block widens from three ports to six, which registryTest noticed: it had been asking about port 0x05 precisely BECAUSE nothing was there, and the console had just moved in. Re-blessing it would have left it checking nothing, so it asks about 0x80 instead - clear of the console, the disk, the screen, the controller, and the sound device coming to 0x40. Six checks in Tests/video.sh swapped from the sequences to the registers, including that the cursor reads back and that one sent past the edge is clamped rather than refusing. Those checks also stopped counting bytes from the ends of a file, which had quietly started measuring an escape the moment the console began announcing the cursor. SplitLint caught the one thing worth catching in the port: the clear command leaves A at 1 and key mode is also 1, so the second load looks redundant. Acting on it would tie a console command to a console mode by coincidence, and break silently if either ever moved, so it is suppressed with that reason rather than removed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
co-authored by
Claude Opus 5
parent
bdb2d0d8e6
commit
43a05b3df1
@@ -36,8 +36,8 @@
|
||||
|
||||
start:
|
||||
CALL seedGlider
|
||||
SETD.0 ClearScreen
|
||||
CALL printString
|
||||
INIA 0x01
|
||||
OUTA 0x05 ; Console command: clear the screen
|
||||
SETD.3 GenerationsLeft
|
||||
INIA 0xFF
|
||||
STA.3
|
||||
@@ -126,8 +126,9 @@ seedGlider:
|
||||
RET
|
||||
|
||||
renderBoard:
|
||||
SETD.0 CursorHome
|
||||
CALL printString
|
||||
RSTA
|
||||
OUTA 0x03
|
||||
OUTA 0x04 ; Cursor to row 0, column 0
|
||||
SETD.1 RowCount
|
||||
SETD.2 ColCount
|
||||
INIA 0d16
|
||||
@@ -371,12 +372,6 @@ RanOutText:
|
||||
StoppedText:
|
||||
"stopped"
|
||||
|
||||
ClearScreen:
|
||||
0x1B
|
||||
"[2J"
|
||||
CursorHome:
|
||||
0x1B
|
||||
"[H"
|
||||
|
||||
; 18 by 18 cells with the current and next states interleaved, so 648 bytes. The
|
||||
; original leaves this implicit and leans on Data Memory being zero, which works but
|
||||
|
||||
@@ -60,13 +60,17 @@ start:
|
||||
CALL placeSnake
|
||||
CALL placeFood
|
||||
|
||||
SETD.0 ClearScreen
|
||||
CALL printString
|
||||
INIA 0x01
|
||||
OUTA 0x05 ; Console command: clear the screen
|
||||
|
||||
; Key mode, so that one key is one byte and arrives when it is pressed. It is put back
|
||||
; before this returns, and CosmOS puts it back too in case a program stops without
|
||||
; doing so.
|
||||
INIA 0x01
|
||||
;
|
||||
; A is already 1 from the clear above, and leaving this out would save a byte by tying a
|
||||
; console COMMAND to a console MODE that happens to share a number. That is a coincidence
|
||||
; rather than a saving, and it would break silently if either ever moved.
|
||||
INIA 0x01 ; splitlint[redundant-assignment]: see above
|
||||
OUTA 0x02
|
||||
|
||||
gameLoop:
|
||||
@@ -529,8 +533,9 @@ removeTail:
|
||||
; The whole board, every frame, from the top left corner. Sixteen by sixteen is small
|
||||
; enough that working out what changed would cost more than sending it all again.
|
||||
draw:
|
||||
SETD.0 CursorHome
|
||||
CALL printString
|
||||
RSTA
|
||||
OUTA 0x03
|
||||
OUTA 0x04 ; Cursor to row 0, column 0
|
||||
SETD.0 BorderText
|
||||
CALL printString
|
||||
CALL newLine
|
||||
@@ -642,12 +647,6 @@ RandomSeed:
|
||||
RandomState:
|
||||
0x00 0x00
|
||||
|
||||
ClearScreen:
|
||||
0x1B
|
||||
"[2J"
|
||||
CursorHome:
|
||||
0x1B
|
||||
"[H"
|
||||
|
||||
BorderText:
|
||||
"+----------------+"
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
|
||||
start:
|
||||
CALL seedGlider
|
||||
SETD ClearScreen
|
||||
CALL printString
|
||||
INIA 0x01
|
||||
OUTA 0x05 ; Console command: clear the screen
|
||||
|
||||
generationLoop:
|
||||
CALL renderBoard
|
||||
@@ -48,8 +48,9 @@ seedGlider:
|
||||
RET
|
||||
|
||||
renderBoard:
|
||||
SETD CursorHome
|
||||
CALL printString
|
||||
RSTA
|
||||
OUTA 0x03
|
||||
OUTA 0x04 ; Cursor to row 0, column 0
|
||||
SETD Board
|
||||
DPUP 0d38
|
||||
INIA 0d16
|
||||
@@ -306,12 +307,6 @@ RowCount:
|
||||
ColCount:
|
||||
0x00
|
||||
|
||||
ClearScreen:
|
||||
0x1B
|
||||
"[2J"
|
||||
CursorHome:
|
||||
0x1B
|
||||
"[H"
|
||||
|
||||
; The emulator zero-fills the remainder of Data Memory. Board names the first
|
||||
; byte of a 648-byte logical allocation (18 * 18 * 2).
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
|
||||
start:
|
||||
CALL seedGlider
|
||||
SETD.0 ClearScreen
|
||||
CALL printString
|
||||
INIA 0x01
|
||||
OUTA 0x05 ; Console command: clear the screen
|
||||
|
||||
generationLoop:
|
||||
CALL renderBoard
|
||||
@@ -38,8 +38,9 @@ seedGlider:
|
||||
RET
|
||||
|
||||
renderBoard:
|
||||
SETD.0 CursorHome
|
||||
CALL printString
|
||||
RSTA
|
||||
OUTA 0x03
|
||||
OUTA 0x04 ; Cursor to row 0, column 0
|
||||
SETD.1 RowCount
|
||||
SETD.2 ColCount
|
||||
INIA 0d16
|
||||
@@ -250,12 +251,6 @@ RowCount:
|
||||
ColCount:
|
||||
0x00
|
||||
|
||||
ClearScreen:
|
||||
0x1B
|
||||
"[2J"
|
||||
CursorHome:
|
||||
0x1B
|
||||
"[H"
|
||||
|
||||
; 18 by 18 cells with the current and next states interleaved, so 648 bytes. The
|
||||
; original leaves this implicit and leans on Data Memory being zero, which works but
|
||||
|
||||
@@ -6,10 +6,15 @@
|
||||
; about, which is the whole point: reading port 0x00 to find out what it is would take
|
||||
; a character off standard input and wait for one that never comes.
|
||||
;
|
||||
; Port 0x05 has nothing on it, and reads as class 0. That is the same answer a machine
|
||||
; Port 0x80 has nothing on it, and reads as class 0. That is the same answer a machine
|
||||
; with no registry at all would give, so software finds out whether it can enumerate by
|
||||
; enumerating.
|
||||
;
|
||||
; It used to ask about 0x05, until the console grew cursor registers and took it. The empty
|
||||
; port has to be one nothing is likely to want: 0x80 is clear of the console below it, the
|
||||
; disk and the screen, the memory controller at the top, and the sound device that is coming
|
||||
; to 0x40.
|
||||
;
|
||||
; Correct output is:
|
||||
; 00 02 00 the console, class 2, no flags
|
||||
; 10 10 00 the test device, class 0x10, no flags
|
||||
@@ -27,7 +32,7 @@ start:
|
||||
CALL reportPort
|
||||
INIA 0xFF
|
||||
CALL reportPort
|
||||
INIA 0x05
|
||||
INIA 0x80
|
||||
CALL reportPort
|
||||
HALT
|
||||
|
||||
|
||||
Reference in New Issue
Block a user