Scroll the screen sideways, and by less than a cell

The screen could move one way, a cell at a time. Three registers were
missing and this adds them: a column origin so the map can be wider than
the screen as well as taller, and a pixel remainder for each axis so the
step can be one pixel rather than eight.

  0x36  Scroll column, in cells, wrapping at 128
  0x37  Fine X, 0 to 7 pixels
  0x38  Fine Y, 0 to 7 pixels

FINE DOES NOT CARRY INTO COARSE. Writing 8 to a fine register writes 0,
because only its low three bits mean anything. The alternative was for a
write of 8 to step the coarse register, and it was rejected for one reason:
a program that scrolls has to know where it has got to, and if the hardware
carries then the only way to find out is to read the register back. Keeping
them apart means the program already knows, because it did the arithmetic
itself. It is also what the machines this one is pretending to be did.

The renderer now draws one more row and one more column than fit and clips
them, because with a fine offset the screen no longer begins on a cell
boundary and the cells at two edges are partly off it.

videoPutCell follows the column origin as it has always followed the row -
a caller means a cell of the SCREEN, and the screen is a window onto the
map. The fine offsets are deliberately not applied there: they move the
finished picture by less than a cell, and there is no such thing as less
than a cell to write into. So a program may scroll to any pixel without the
console's idea of where row three, column five is moving underneath it.

Grid now scrolls diagonally, a pixel a frame, in four port writes and two
carries. It moved eight pixels every fourth frame before, which reads as
the picture jumping rather than travelling.

Seven checks, each one the same program with one register changed, so what
is compared is where the picture stopped. Breaking fine X, fine Y, the
column origin, the three-bit mask, or the console's use of the origin each
fails exactly one of them.

Grid's own two checks had to be rewritten, and the reason is worth keeping:
they asked whether pixel 4 was a grid line, which was really a check that
the scroll happened to be at a cell boundary. A picture that moves a pixel
a frame can only be asked things that are true at every offset - that it
repeats every eight pixels, and that one band of eight rows holds different
colours from the next.

Also repairs docs.sh, which found the minimal CosmOS application by taking
the first asm block in the README. Documenting a program with an example
above it made that a different block, and the check complained that the
minimal application had no #Base about something that never claimed to be
one. It looks under System Services now.

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 18:52:15 -04:00
co-authored by Claude Opus 5
parent 848103f5e4
commit bcd42e75ca
10 changed files with 329 additions and 61 deletions
+54 -30
View File
@@ -48,13 +48,9 @@ start:
; ---- The loop ----
;
; Wait for the screen to finish a frame, count four of them, move the origin down one row.
; That is fifteen rows a second, which is slow enough to watch and fast enough to look
; deliberate.
RSTA
SETD.0 Ticks
STA.0
; A frame, then one pixel down and one across. This used to move a whole cell every fourth
; frame, because a cell was as fine as the screen could be moved - eight pixels at a time,
; which reads as the picture jumping rather than travelling.
everyFrame:
CALL waitFrame
@@ -65,30 +61,49 @@ everyFrame:
AND
BNQ finished
SETD.0 Ticks
; ---- A pixel down, and the cell it belongs to ----
;
; Fine is the low three bits of the register and does not carry, so this does: eight steps
; inside the cell and then one step of the origin. The AND is both the wrap and the test -
; Q coming out as nought is exactly the moment the cell boundary was crossed.
SETD.0 FineDown
LDA.0
INCA
STA.0
INIB 0d4
CCF
SUB
BNQ everyFrame
INIB 0x07
AND
STQ.0
OUTQ 0x38
BNQ stepAcross
RSTA
STA.0 ; DP0 is still Ticks, from the count just above.
; ---- The scroll itself ----
;
; One byte. The map is 128 rows and the screen shows 25 of them, so this walks the origin
; through a ring: what leaves the top has not gone anywhere and comes back round.
SETD.0 Origin
; The map is 128 rows against a screen of 25 or 50, so the origin walks a ring: what leaves
; the top has not gone anywhere and comes back round.
SETD.0 OriginDown
LDA.0
INCA
INIB 0x7F
AND
MVQA
STA.0
OUTA 0x34
STQ.0
OUTQ 0x34
stepAcross:
; And the same sideways, which is the axis that did not exist at all until now. There are
; 128 columns against the 80 shown, so this ring is shallower but it is the same ring.
SETD.0 FineAcross
LDA.0
INCA
INIB 0x07
AND
STQ.0
OUTQ 0x37
BNQ everyFrame
SETD.0 OriginAcross
LDA.0
INCA
INIB 0x7F
AND
STQ.0
OUTQ 0x36
BRI everyFrame
finished:
@@ -97,11 +112,16 @@ finished:
; ---- Putting the screen back ----
;
; The origin first, then every cell of the map and not just the visible ones. A console
; that scrolls would otherwise walk down into rows this program filled, and find a grid
; underneath its own output.
; All four scroll registers, or the shell inherits a view that begins half way into a cell.
RSTA
OUTA 0x34
OUTA 0x36
OUTA 0x37
OUTA 0x38
; The row origin too, and then every cell of the map and not just the visible ones. A
; console that scrolls would otherwise walk down into rows this program filled, and find a
; grid underneath its own output.
OUTA 0x34 ; A is still nought, from the three above.
INIA 0d3
OUTA 0xE3
@@ -365,9 +385,13 @@ everyCell:
#Base 0x2000
Ticks:
FineDown:
0x00
Origin:
FineAcross:
0x00
OriginDown:
0x00
OriginAcross:
0x00
Scheme:
0x00
+24 -5
View File
@@ -531,7 +531,7 @@ from every assembly file in it. Several are old programs written for the bare ma
| Settle | Says how the last start went and tells the machine to stop falling back, in 353 bytes. A program rather than a shell word, because the shell is for what cannot be done without it. |
| Files | Writes a file, reads it back, renames it and deletes it, in 675 bytes, including nothing but the service names. It is what says a program does not need a filesystem inside it. |
| Break | Stops itself twice with SWI osBreak, so that the registers can be seen changing between one stop and the next. |
| Grid | The first program to use the screen as a screen. Redefines a tile above the font, fills all 128 map rows, and scrolls by writing one byte a frame. |
| Grid | The first program to use the screen as a screen. Redefines a tile above the font, fills all 128 map rows, and scrolls it diagonally a pixel at a time. |
| Edit | A line editor. |
| Stream | Reads an 84,000 byte file through a buffer of 256, which is what says a file bigger than Data Memory can be read at all. |
| Type | Prints a named text file a block at a time, including one too large to fit in Data Memory. |
@@ -685,10 +685,29 @@ use when the thing being debugged is the boot chain, since it skips the boot cha
Everything else drawn on this machine has been text or a bitmap. `Grid` is the first program
to use the tile engine as an engine, and it is worth reading for the size of the numbers.
It writes **one byte a frame** to scroll. The map is 128 rows and the screen shows 25, so the
rows above and below are already drawn - scrolling moves the origin rather than 2,000 bytes of
screen, and the rows that leave the top have not gone anywhere. A screenful of movement costs
one `OUTA`.
It scrolls **a pixel a frame, diagonally**, and the whole of that is four port writes and two
carries. The map is 128 rows and 128 columns against a screen of 50 and 80, so the cells
around the edge are already drawn - scrolling moves the origin rather than 2,000 bytes of
screen, and what leaves the top has not gone anywhere.
The coarse registers move a whole cell and the fine ones move the remainder, and **they do not
carry into each other**, so the program does:
```asm
SETD.0 FineDown
LDA.0
INCA
INIB 0x07
AND
STQ.0
OUTQ 0x38
BNQ stepAcross ; Still inside the cell.
; ... and here, one step of the row origin.
```
The `AND` is both the wrap and the test: `Q` coming out as nought is exactly the moment the
cell boundary was crossed. It moved eight pixels every fourth frame before the fine registers
existed, which reads as the picture jumping rather than travelling.
It puts its tile at **200**, because the machine wakes with the font in tile memory - glyph n
at tile n, for 135 of the 256 - so a program that starts at zero paints over the alphabet and