A second screen, and one port to say which is shown

A screen drawn where it can be seen is seen half drawn. A program that
moves forty things and rewrites the map underneath them is wrong for as
long as it takes to put them all right, and at a megahertz that is long
enough to look at.

So the device brings a second screen bank, on port 0x3B, and port 0x3C
says which of the two is displayed. Everything a program draws into the
other one is invisible until one byte shows the whole of it at once.

ONE REGISTER IS ENOUGH, where the hardware this imitates needed two. The
other said which screen the CPU's window pointed at; there is no window
here, because a program reaches a bank through the memory controller by
its number. Writing to the screen that is not shown is a matter of naming
its bank, and the device never has to be told.

And a flip cannot tear: a frame is drawn from one bank in one go, so a
flip either happened before that frame or happens before the next. There
is nothing to race, where the real machines had to catch the few lines
between frames to swap in.

The console draws into whichever screen is displayed rather than one of
its own, so a fault message lands where somebody can read it even if a
game had flipped. And CosmOS puts the displayed screen back at exit, the
way it already puts back the cursor and the ink: a program that faulted
while flipped could not have, and a shell that only came out right for
programs which remembered would come out wrong the day one crashed.

Flip.asm is the worked example. It deliberately does NOT restore the
display itself - that is the point of the paragraph above, and it is what
makes the system's guarantee the thing under test rather than the
program's good manners. Written the other way round first, where it
passed with the guarantee deleted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
Anachronaut
2026-09-02 10:31:29 -04:00
co-authored by Claude Opus 5
parent 66e7b84272
commit 023362b05a
17 changed files with 399 additions and 37 deletions
+32 -12
View File
@@ -176,18 +176,37 @@ int videoTextRows(void);
// Copy the sixteen ink and paper pairs back, leaving the rest of the palette alone.
#define VIDEO_COMMAND_PALETTE 0x02
// ---- The port that owns the screen bank ----
// ---- The ports that own banks ----
//
// A bank is registered by naming THE PORT THAT OWNS IT, which the controller settled long
// before the screen had two of them. So a device with two banks needs two ports that own
// memory, and needs no new mechanism at all: the base port owns the atlas, and this one
// owns the screen.
// before the screen had more than one. So a device with several banks needs several ports
// that own memory, and needs no new mechanism at all.
//
// The base port keeps the atlas rather than the screen because tiles have been at 0x0000
// since there was a screen at all, and whichever way round this went, one of the two
// meanings had to move. Nothing is READ OR WRITTEN here - it is a name for a bank, and the
// registry is where a program finds out it brings one.
#define VIDEO_SCREEN 0x3A
// The base port keeps the atlas rather than a screen because tiles have been at 0x0000 since
// there was a screen at all, and whichever way round that went, one of the two meanings had
// to move. Nothing is READ OR WRITTEN at any of these - they are names for banks, and the
// registry is where a program finds out which of them bring one.
#define VIDEO_SCREEN0 0x3A
#define VIDEO_SCREEN1 0x3B
// ---- Two screens, and only one register to say which ----
//
// A back buffer is a whole screen's worth of map written where nobody can see it, and then
// shown all at once. It is what stops a picture being seen half finished - a game that moves
// forty sprites and rewrites the map underneath them is not finished being wrong until the
// last of them has been put right.
//
// THE DEVICE ONLY NEEDS TO KNOW WHICH IS DISPLAYED. Real machines needed a second register
// saying which one the CPU's window pointed at; there is no window here, because a program
// reaches a bank through the memory controller BY ITS NUMBER. Writing to the one that is not
// being shown is a matter of naming its bank, and the screen never has to be told.
//
// A FLIP CANNOT TEAR. A frame is drawn from one bank in one go, so a flip either happened
// before that frame or it happens before the next one; there is no state of having flipped
// halfway. That is worth saying because it is the thing the hardware this imitates had to
// work for, with an interrupt and a register written in the few lines between frames.
#define VIDEO_DISPLAY 0x3C
#define VIDEO_SCREEN_COUNT 2
// Eight pixels to a cell, so three bits say where inside one the view begins.
#define VIDEO_FINE_MASK 0x07
@@ -265,9 +284,10 @@ void videoPutCell(int screenRow, int column, uint8_t tile, uint8_t attribute);
// bottom - which is holding whatever was there 128 rows ago, since the map is a ring.
void videoScrollUp(void);
// The memory behind one of the device's two ports: VIDEO_STATUS for the atlas, VIDEO_SCREEN
// for the screen. NULL for any other port, because the rest of the block owns no memory and
// registering a bank onto one would put a number in the table that leads nowhere.
// The memory behind one of the device's memory-owning ports: VIDEO_STATUS for the atlas,
// VIDEO_SCREEN0 and VIDEO_SCREEN1 for the two screens. NULL for any other port, because the
// rest of the block owns no memory and registering a bank onto one would put a number in the
// table that leads nowhere.
uint8_t *videoMemory(uint8_t port, uint32_t *capacity);
uint8_t videoWrite(uint8_t value, uint8_t port);