Grid: the first program to use the screen as a screen
Everything drawn on this machine so far has been text or a bitmap. The tile
engine has been there since the screen was built and only the console had
touched it, and only ever to put a letter in a cell - the one thing it can
do that a plain character display could do too.
Grid redefines a tile, fills all 128 map rows with it, and scrolls by
writing ONE BYTE A FRAME. Nothing moves. The rows above and below the
screen are already drawn, so a screenful of movement costs one OUTA and the
rows that leave the top are still there.
Its tile goes 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 starting at zero
paints over the alphabet and the shell it is about to hand the machine back
to. Its sixteen colour bands are one tile and not sixteen: the attribute
nibble is added to every index in a cell, so the same 64 bytes come out in
sixteen colourings.
Three things it cost, all of them the same lesson about this machine:
- "SETD.0 X" then "STD.0.1" stores through DP1, which had not been set
yet. It assembles, and the blit then reads its 64 bytes from wherever
DP1 was last left, so the tile came out as noise.
- The palette entry for scheme n is at 0xFC00 + 64n, which reaches
0xFFC0 - four pages, not one. And doubling A by adding B needs B to
hold A, which RSTB is the opposite of. Both went away by writing all
256 entries in order and letting the controller step the address, so
nothing computes an address at all.
- The screen it hands back had the right cells and the wrong colours,
because restoring the map is not restoring the palette.
That last one is a gap in the machine rather than in this program, and is
written up in the CosmOS README. The console's colours live at exactly the
entries the attribute nibble lands on, so any program using the nibble
overwrites them and has nowhere else to write. Grid puts bank 0 back - grey
on black - and leaves the other fifteen. The real answer is a command to
the screen meaning "give me back what you woke up with", the way the
console has one for clearing. There is not one, and this is the first
program that ever wanted it.
Two checks in video.sh, which boots the whole system and reads the pixels
the renderer produced rather than trusting what the program believed.
Breaking the tile fails one and breaking the attribute fails the other.
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
0e0731e2b1
commit
1a8a5efe03
@@ -0,0 +1,395 @@
|
||||
; Grid, the first program to use the screen as a screen.
|
||||
;
|
||||
; Everything drawn on this machine so far has been text or a bitmap. The tile engine has
|
||||
; been there since the screen was built and nothing had touched it: the console uses it,
|
||||
; but only ever to put a letter in a cell, which is the one thing it can do that a plain
|
||||
; character display could do too.
|
||||
;
|
||||
; This redefines a tile, fills a map bigger than the screen with it, and then scrolls that
|
||||
; map by writing ONE BYTE A FRAME. No memory moves. The rows above and below the screen are
|
||||
; already there, so what a scroll costs is not the 2,000 bytes of a screenful but the one
|
||||
; byte that says which row is on top.
|
||||
;
|
||||
; ---- Where its tiles live ----
|
||||
;
|
||||
; At 200, and the font is why. The machine wakes with the font in tile memory - glyph n at
|
||||
; tile n, for 135 of the 256 - so a program that starts writing tiles at zero paints over
|
||||
; the alphabet and the shell it is going to hand the machine back to. Above 135 is empty and
|
||||
; nobody else's, so nothing here has to be put back afterwards except the map.
|
||||
;
|
||||
; Written by Anachronaut
|
||||
|
||||
#Include services.asm
|
||||
|
||||
#Program
|
||||
|
||||
#Base 0x4000
|
||||
|
||||
start:
|
||||
; ---- Reaching video memory ----
|
||||
;
|
||||
; The CPU cannot touch it. It belongs to the device, and the only way in is to give it a
|
||||
; bank number and go through the memory controller - the same as the disk's buffer.
|
||||
INIA 0d3
|
||||
OUTA 0xE3 ; DestBank: the number it will answer to.
|
||||
INIA 0x30
|
||||
OUTA 0xE2 ; SourceLow: the port of the device that owns it.
|
||||
INIA 0x03
|
||||
OUTA 0xE8 ; RegisterBank.
|
||||
|
||||
CALL putTile
|
||||
CALL putPalette
|
||||
CALL putMap
|
||||
|
||||
; Key mode, so that a key arrives when it is pressed rather than when Return is. Put back
|
||||
; before this returns, and CosmOS puts it back too if a program forgets.
|
||||
INIA 0x01
|
||||
OUTA 0x02
|
||||
|
||||
; ---- 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
|
||||
|
||||
everyFrame:
|
||||
CALL waitFrame
|
||||
|
||||
; Anything typed ends it. Asked for, never waited for: the console holds the key until
|
||||
; somebody wants it, so nothing pressed between frames is lost.
|
||||
INA 0x01
|
||||
INIB 0x01 ; READY
|
||||
AND
|
||||
BNQ finished
|
||||
|
||||
SETD.0 Ticks
|
||||
LDA.0
|
||||
INCA
|
||||
STA.0
|
||||
INIB 0d4
|
||||
CCF
|
||||
SUB
|
||||
BNQ everyFrame
|
||||
|
||||
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
|
||||
LDA.0
|
||||
INCA
|
||||
INIB 0x7F
|
||||
AND
|
||||
MVQA
|
||||
STA.0
|
||||
OUTA 0x34
|
||||
BRI everyFrame
|
||||
|
||||
finished:
|
||||
; The key that stopped it, taken so the shell does not find it waiting.
|
||||
INA 0x00
|
||||
|
||||
; ---- 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.
|
||||
RSTA
|
||||
OUTA 0x34
|
||||
|
||||
INIA 0d3
|
||||
OUTA 0xE3
|
||||
INIA 0x40
|
||||
OUTA 0xE4
|
||||
RSTA
|
||||
OUTA 0xE5
|
||||
OUTA 0xE2 ; The byte to write: tile 0 is the space, attribute 0 is plain.
|
||||
INIA 0x80
|
||||
OUTA 0xE6
|
||||
RSTA
|
||||
OUTA 0xE7 ; 0x8000 bytes, which is the whole map.
|
||||
INIA 0x02
|
||||
OUTA 0xE8 ; Fill.
|
||||
|
||||
; ---- And the colours it woke up with ----
|
||||
;
|
||||
; Only the first pair, and that is worth being honest about. The console's own scheme is
|
||||
; sixteen banks - colours on black in 0 to 7, the same colours inverted in 8 to 15 - and
|
||||
; this program wrote over all of them, because the attribute nibble lands on exactly the
|
||||
; entries the console uses and there is nowhere else for it to land. Putting all thirty two
|
||||
; back would mean copying the device's own table into an application, which is the kind of
|
||||
; duplication that goes stale the first time somebody picks a nicer green.
|
||||
;
|
||||
; So it restores bank 0, grey on black, which is what plain text has always been and what
|
||||
; the shell will be using when it gets the machine back. The other fifteen keep this
|
||||
; program's colours until something else writes them, which is visible only to a program
|
||||
; that sets the console's attribute port.
|
||||
;
|
||||
; THE REAL ANSWER IS A COMMAND TO THE SCREEN saying "give me back what you woke up with",
|
||||
; the same way the console has one for clearing. There is not one, and this program is the
|
||||
; first thing that ever wanted it.
|
||||
INIA 0d3
|
||||
OUTA 0xE3
|
||||
INIA 0xFC
|
||||
OUTA 0xE4
|
||||
RSTA
|
||||
OUTA 0xE5
|
||||
OUTA 0xE9 ; Entry 0, the paper: black.
|
||||
OUTA 0xE9
|
||||
OUTA 0xE9
|
||||
OUTA 0xE9
|
||||
INIA 0xD8
|
||||
OUTA 0xE9 ; Entry 1, the ink: grey.
|
||||
OUTA 0xE9
|
||||
OUTA 0xE9
|
||||
RSTA
|
||||
OUTA 0xE9
|
||||
|
||||
OUTA 0x02 ; Line mode, the way it was found.
|
||||
RSTA ; splitlint[redundant-assignment]: an exit status, not a mode
|
||||
SWI osExit
|
||||
|
||||
; ---- A frame ----
|
||||
;
|
||||
; Asked for rather than waited on with an interrupt. Polling costs a program nothing it
|
||||
; needs here and saves installing a vector, and the status bit is honest: reading it is what
|
||||
; answers it, so this cannot see the same frame twice.
|
||||
waitFrame:
|
||||
INA 0x30
|
||||
INIB 0x01
|
||||
AND
|
||||
BRQ waitFrame
|
||||
RET
|
||||
|
||||
; ---- The tile ----
|
||||
;
|
||||
; Blitted rather than written a byte at a time, because it is already sixty four bytes of
|
||||
; Data Segment and the controller will move it in one command. Tile n starts at n times 64,
|
||||
; so tile 200 starts at 12,800, which is 0x3200.
|
||||
putTile:
|
||||
INIA 0x01
|
||||
OUTA 0xE0 ; SourceBank: Data Memory.
|
||||
; The pointer BEFORE storing through it. Written the other way round the first time, which
|
||||
; assembles perfectly and stores the address into wherever DP1 was last left - so the blit
|
||||
; read its sixty four bytes from nowhere in particular and tile 200 came out as noise.
|
||||
SETD.1 TileArtAt
|
||||
SETD.0 TileArt
|
||||
STD.0.1
|
||||
LDA.1
|
||||
OUTA 0xE1
|
||||
INCD.1
|
||||
LDA.1
|
||||
OUTA 0xE2
|
||||
INIA 0d3
|
||||
OUTA 0xE3
|
||||
INIA 0x32
|
||||
OUTA 0xE4
|
||||
RSTA
|
||||
OUTA 0xE5
|
||||
OUTA 0xE6
|
||||
INIA 0d64
|
||||
OUTA 0xE7
|
||||
INIA 0x01
|
||||
OUTA 0xE8 ; Blit.
|
||||
RET
|
||||
|
||||
; ---- The colours ----
|
||||
;
|
||||
; The tile is drawn in indices 0 and 1, and the low nibble of a cell's attribute is ADDED to
|
||||
; every index in it, sixteen at a time. So the same sixty four bytes appear in sixteen colour
|
||||
; schemes, and what this writes is those schemes: entry 16n is the ground and 16n+1 the line.
|
||||
; Nothing is duplicated to get them.
|
||||
;
|
||||
; ---- Nothing here works out an address ----
|
||||
;
|
||||
; The first try computed where entry 16n lives - 0xFC00 plus 64n - and was wrong twice for
|
||||
; the same reason, which is that this machine cannot multiply and pretending otherwise is
|
||||
; where the bugs go. 64n reaches 960, so the address spans four pages and the high byte moves
|
||||
; too; and doubling A by adding B to it needs B to hold A first, which RSTB is the opposite
|
||||
; of.
|
||||
;
|
||||
; So it writes all 256 entries in order and never computes anything. The controller's Data
|
||||
; port steps the address on after every byte, so the whole palette is one sweep of 1,024
|
||||
; writes with no arithmetic in it at all. The colours that change do so by adding sixteen to
|
||||
; a running value, which is the same reason.
|
||||
putPalette:
|
||||
INIA 0d3
|
||||
OUTA 0xE3
|
||||
INIA 0xFC
|
||||
OUTA 0xE4
|
||||
RSTA
|
||||
OUTA 0xE5
|
||||
|
||||
SETD.0 Scheme
|
||||
STA.0
|
||||
SETD.0 LineRed
|
||||
STA.0
|
||||
INIA 0xFF
|
||||
SETD.0 LineGreen
|
||||
STA.0
|
||||
|
||||
everyScheme:
|
||||
; Entry 16n, the ground: the same dark under every scheme.
|
||||
INIA 0d16
|
||||
OUTA 0xE9
|
||||
OUTA 0xE9
|
||||
INIA 0d24
|
||||
OUTA 0xE9
|
||||
RSTA
|
||||
OUTA 0xE9
|
||||
|
||||
; Entry 16n+1, the line: more red and less green the further down the map it is, so that
|
||||
; scrolling is visibly going somewhere rather than showing the same row again.
|
||||
SETD.0 LineRed
|
||||
LDA.0
|
||||
OUTA 0xE9
|
||||
SETD.0 LineGreen
|
||||
LDA.0
|
||||
OUTA 0xE9
|
||||
INIA 0d96
|
||||
OUTA 0xE9
|
||||
RSTA
|
||||
OUTA 0xE9
|
||||
|
||||
; The fourteen this tile never asks for. Written anyway, because the sweep is what keeps
|
||||
; the address right and skipping them would mean working one out.
|
||||
INIA 0d14
|
||||
SETD.0 Spare
|
||||
STA.0
|
||||
everySpare:
|
||||
RSTA
|
||||
OUTA 0xE9
|
||||
OUTA 0xE9
|
||||
OUTA 0xE9
|
||||
OUTA 0xE9
|
||||
SETD.0 Spare
|
||||
LDA.0
|
||||
DECA
|
||||
STA.0
|
||||
BNA everySpare
|
||||
|
||||
SETD.0 LineRed
|
||||
LDA.0
|
||||
INIB 0d16
|
||||
CCF
|
||||
ADD
|
||||
STQ.0
|
||||
SETD.0 LineGreen
|
||||
LDA.0
|
||||
CCF
|
||||
SUB ; B is still sixteen, from the red just above.
|
||||
STQ.0
|
||||
|
||||
SETD.0 Scheme
|
||||
LDA.0
|
||||
INCA
|
||||
STA.0
|
||||
CCF
|
||||
SUB ; And still sixteen here, which is also how many schemes there are.
|
||||
BNQ everyScheme
|
||||
RET
|
||||
|
||||
; ---- The map ----
|
||||
;
|
||||
; All 128 rows, not the 25 the screen shows. That is the whole point of a map bigger than
|
||||
; the screen: the rows above and below are already drawn, so scrolling is a change of origin
|
||||
; rather than a change of anything.
|
||||
;
|
||||
; A ROW IS A PAGE, which is why the row number is written straight into DestHigh and the
|
||||
; column arithmetic disappears. The controller's Data port steps the address on after every
|
||||
; byte, so a row is a loop over two writes with no address handling in it at all.
|
||||
putMap:
|
||||
RSTA
|
||||
SETD.0 MapRow
|
||||
STA.0
|
||||
|
||||
everyRow:
|
||||
INIA 0d3
|
||||
OUTA 0xE3
|
||||
SETD.0 MapRow
|
||||
LDA.0
|
||||
INIB 0x40
|
||||
CCF
|
||||
ADD
|
||||
OUTQ 0xE4 ; The map starts at 0x4000 and a row is a page.
|
||||
RSTA
|
||||
OUTA 0xE5
|
||||
|
||||
; The attribute is the row number's low nibble, so the schemes band down the map and
|
||||
; repeat every sixteen rows. DP0 is still MapRow, from the row's address above.
|
||||
LDA.0
|
||||
INIB 0x0F
|
||||
AND
|
||||
SETD.0 RowAttribute
|
||||
STQ.0
|
||||
|
||||
INIA 0d40
|
||||
SETD.0 RowCells
|
||||
STA.0
|
||||
|
||||
everyCell:
|
||||
INIA 0d200
|
||||
OUTA 0xE9
|
||||
SETD.0 RowAttribute
|
||||
LDA.0
|
||||
OUTA 0xE9
|
||||
SETD.0 RowCells
|
||||
LDA.0
|
||||
DECA
|
||||
STA.0
|
||||
BNA everyCell
|
||||
|
||||
SETD.0 MapRow
|
||||
LDA.0
|
||||
INCA
|
||||
STA.0
|
||||
INIB 0x80
|
||||
CCF
|
||||
SUB
|
||||
BNQ everyRow
|
||||
RET
|
||||
|
||||
#Data
|
||||
|
||||
#Base 0x2000
|
||||
|
||||
Ticks:
|
||||
0x00
|
||||
Origin:
|
||||
0x00
|
||||
Scheme:
|
||||
0x00
|
||||
Spare:
|
||||
0x00
|
||||
LineRed:
|
||||
0x00
|
||||
LineGreen:
|
||||
0x00
|
||||
MapRow:
|
||||
0x00
|
||||
RowAttribute:
|
||||
0x00
|
||||
RowCells:
|
||||
0x00
|
||||
TileArtAt:
|
||||
0x00 0x00
|
||||
|
||||
; ---- Eight by eight, a byte a pixel ----
|
||||
;
|
||||
; A line along the top and one down the left. Tiled edge to edge they meet, so a screenful
|
||||
; of this one tile is a continuous grid rather than 1,000 separate boxes.
|
||||
TileArt:
|
||||
0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01
|
||||
0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00
|
||||
0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00
|
||||
0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00
|
||||
0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00
|
||||
0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00
|
||||
0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00
|
||||
0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00
|
||||
@@ -531,6 +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. |
|
||||
| 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. |
|
||||
@@ -679,6 +680,31 @@ none, and a clean install having nothing to configure is the right default.
|
||||
outside with nothing on the disk consulted. That is what a debugger does, and it is what to
|
||||
use when the thing being debugged is the boot chain, since it skips the boot chain.
|
||||
|
||||
### Grid, and what a tile engine costs:
|
||||
|
||||
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 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
|
||||
the shell it is about to hand the machine back to. Above 135 is empty and nobody else's.
|
||||
|
||||
Its sixteen colour schemes are **one tile**, not sixteen. A cell's attribute nibble is added
|
||||
to every palette index in it, sixteen at a time, so the same 64 bytes come out in sixteen
|
||||
colourings and the map bands down the screen as it scrolls.
|
||||
|
||||
**What it cannot give back is the palette.** The console's colours are sixteen banks at
|
||||
exactly the entries the attribute nibble lands on, so any program using the nibble overwrites
|
||||
them and there is nowhere else for it to write. `Grid` restores bank 0 - grey on black, what
|
||||
plain text has always been - and leaves the other fifteen as it made them. The proper answer
|
||||
is a command to the screen meaning "give me back what you woke up with", the way the console
|
||||
has one for clearing. There is not one yet, and this is the first program that ever wanted it.
|
||||
|
||||
## The Application Model:
|
||||
|
||||
CosmOS divides the two SplitBit address spaces by convention:
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
#! shell
|
||||
#quiet
|
||||
clear
|
||||
echo Welcome to CosmOS.
|
||||
echo Ready.
|
||||
Reference in New Issue
Block a user