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:
co-authored by
Claude Opus 5
parent
66e7b84272
commit
023362b05a
@@ -22,6 +22,7 @@ Break.sbx 149
|
||||
Grid.sbx 571
|
||||
Press.sbx 872
|
||||
Mode.sbx 48
|
||||
Flip.sbx 291
|
||||
Crash.sbx 632
|
||||
vars.script 50
|
||||
blocks.script 343
|
||||
@@ -38,7 +39,7 @@ outer.script 376
|
||||
inner.script 44
|
||||
loop.script 35
|
||||
crossed.txt 560
|
||||
25 files, 1 directory
|
||||
26 files, 1 directory
|
||||
> exit
|
||||
halted
|
||||
Execution halted.
|
||||
|
||||
@@ -12,6 +12,7 @@ Break.sbx 149
|
||||
Grid.sbx 571
|
||||
Press.sbx 872
|
||||
Mode.sbx 48
|
||||
Flip.sbx 291
|
||||
Crash.sbx 632
|
||||
vars.script 50
|
||||
blocks.script 343
|
||||
@@ -27,7 +28,7 @@ nonl.script 38
|
||||
outer.script 376
|
||||
inner.script 44
|
||||
loop.script 35
|
||||
24 files, 1 directory
|
||||
25 files, 1 directory
|
||||
> drive 1
|
||||
> dir
|
||||
other.txt 28
|
||||
|
||||
@@ -29,6 +29,7 @@ Break.sbx 149
|
||||
Grid.sbx 571
|
||||
Press.sbx 872
|
||||
Mode.sbx 48
|
||||
Flip.sbx 291
|
||||
Crash.sbx 632
|
||||
vars.script 50
|
||||
blocks.script 343
|
||||
@@ -44,7 +45,7 @@ nonl.script 38
|
||||
outer.script 376
|
||||
inner.script 44
|
||||
loop.script 35
|
||||
24 files, 1 directory
|
||||
25 files, 1 directory
|
||||
> exit
|
||||
halted
|
||||
Execution halted.
|
||||
|
||||
@@ -19,6 +19,7 @@ Break.sbx 149
|
||||
Grid.sbx 571
|
||||
Press.sbx 872
|
||||
Mode.sbx 48
|
||||
Flip.sbx 291
|
||||
Crash.sbx 632
|
||||
vars.script 50
|
||||
blocks.script 343
|
||||
@@ -34,7 +35,7 @@ nonl.script 38
|
||||
outer.script 376
|
||||
inner.script 44
|
||||
loop.script 35
|
||||
24 files, 1 directory
|
||||
25 files, 1 directory
|
||||
> exit
|
||||
halted
|
||||
Execution halted.
|
||||
|
||||
@@ -105,6 +105,7 @@ Break.sbx 149
|
||||
Grid.sbx 571
|
||||
Press.sbx 872
|
||||
Mode.sbx 48
|
||||
Flip.sbx 291
|
||||
Crash.sbx 632
|
||||
vars.script 50
|
||||
blocks.script 343
|
||||
@@ -120,7 +121,7 @@ nonl.script 38
|
||||
outer.script 376
|
||||
inner.script 44
|
||||
loop.script 35
|
||||
24 files, 1 directory
|
||||
25 files, 1 directory
|
||||
> exit
|
||||
halted
|
||||
Execution halted.
|
||||
|
||||
@@ -24,6 +24,7 @@ Break.sbx 149
|
||||
Grid.sbx 571
|
||||
Press.sbx 872
|
||||
Mode.sbx 48
|
||||
Flip.sbx 291
|
||||
Crash.sbx 632
|
||||
vars.script 50
|
||||
blocks.script 343
|
||||
@@ -39,7 +40,7 @@ nonl.script 38
|
||||
outer.script 376
|
||||
inner.script 44
|
||||
loop.script 35
|
||||
24 files, 1 directory
|
||||
25 files, 1 directory
|
||||
> exit
|
||||
halted
|
||||
Execution halted.
|
||||
|
||||
@@ -12,6 +12,7 @@ Break.sbx 149
|
||||
Grid.sbx 571
|
||||
Press.sbx 872
|
||||
Mode.sbx 48
|
||||
Flip.sbx 291
|
||||
Crash.sbx 632
|
||||
vars.script 50
|
||||
blocks.script 343
|
||||
@@ -27,7 +28,7 @@ nonl.script 38
|
||||
outer.script 376
|
||||
inner.script 44
|
||||
loop.script 35
|
||||
24 files, 1 directory
|
||||
25 files, 1 directory
|
||||
> load
|
||||
load what?
|
||||
> load nosuch.sbx
|
||||
|
||||
@@ -10,6 +10,7 @@ Break.sbx 149
|
||||
Grid.sbx 571
|
||||
Press.sbx 872
|
||||
Mode.sbx 48
|
||||
Flip.sbx 291
|
||||
Crash.sbx 632
|
||||
vars.script 50
|
||||
blocks.script 343
|
||||
@@ -25,7 +26,7 @@ nonl.script 38
|
||||
outer.script 376
|
||||
inner.script 44
|
||||
loop.script 35
|
||||
24 files, 1 directory
|
||||
25 files, 1 directory
|
||||
> load Say.sbx
|
||||
loaded, starting at 5000
|
||||
> run the disk took its time
|
||||
|
||||
@@ -124,6 +124,12 @@ for i in 1 2 3 4 5 6 7 8; do "$TOOL" put "$DISKS/sbfs.img" "filler$i.txt" >/dev/
|
||||
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
|
||||
"$ROOT/Programs/CosmOS/Apps/Mode.asm" -o "$WORK/Mode.sbx" >/dev/null
|
||||
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Mode.sbx" >/dev/null
|
||||
# Flip.sbx draws a whole screen into the bank that is not being shown, waits, and then shows
|
||||
# it. It is the only program here that leaves the machine displaying the OTHER screen, which
|
||||
# is what makes it the thing that checks the system puts that back.
|
||||
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
|
||||
"$ROOT/Programs/CosmOS/Apps/Flip.asm" -o "$WORK/Flip.sbx" >/dev/null
|
||||
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Flip.sbx" >/dev/null
|
||||
# Crash.sbx breaks in each of the four ways the system now catches, which is the only way to
|
||||
# reach a fault screen from a recorded test.
|
||||
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
|
||||
|
||||
+113
@@ -63,6 +63,12 @@ start:
|
||||
OUTA 0xE2
|
||||
INIA 0x03
|
||||
OUTA 0xE8 ; And the screen - the map, or a bitmap - becomes bank 4
|
||||
INIA 0d5
|
||||
OUTA 0xE3
|
||||
INIA 0x3B
|
||||
OUTA 0xE2
|
||||
INIA 0x03
|
||||
OUTA 0xE8 ; And the other screen, the one nobody is looking at, bank 5
|
||||
ASM
|
||||
# ---- Said rather than assumed ----
|
||||
#
|
||||
@@ -87,6 +93,7 @@ pokeTo() {
|
||||
|
||||
pokeAtlas() { pokeTo 3 "$1" "$2"; } # Tiles and the palette.
|
||||
pokeScreen() { pokeTo 4 "$1" "$2"; } # The map, or a bitmap.
|
||||
pokeBack() { pokeTo 5 "$1" "$2"; } # The same, in the screen not being shown.
|
||||
|
||||
# Many bytes from one address, using the Data port's own stepping rather than naming the
|
||||
# address again for each. What a run of tile memory is for, and so far only the atlas needs
|
||||
@@ -322,6 +329,74 @@ echo "Checking what the video device draws."
|
||||
&& result ok "the map is a ring" "row 0 follows row 127" \
|
||||
|| result no "the map is a ring" "got $(pixel ring 0 8)"
|
||||
|
||||
# ---- Two screens, and the flip between them ----
|
||||
#
|
||||
# One red cell in each screen, in different rows: row one of the screen being shown, and the
|
||||
# corner of the one that is not. BOTH HALVES ARE CHECKED, because either alone is weak - that
|
||||
# the corner stayed empty would also be true of a bank that went nowhere, and that row one
|
||||
# appeared would also be true of a device with one screen written twice. Together they say
|
||||
# the two banks are different memory and only one of them is the screen.
|
||||
{ prologue
|
||||
pokeAtlas 0xFC04 0xFF; pokeAtlas 0xFC05 0x00; pokeAtlas 0xFC06 0x00
|
||||
for i in $(seq 0 63); do pokeAtlas $((0x0040 + i)) 0x01; done
|
||||
pokeScreen $((0x4000 + 256)) 0x01
|
||||
pokeBack 0x4000 0x01; pokeBack 0x4001 0x00
|
||||
epilogue
|
||||
} | run backbuffer || exit 1
|
||||
[ "$(pixel backbuffer 0 8)" = "255,0,0" ] && [ "$(pixel backbuffer 0 0)" = "0,0,0" ] \
|
||||
&& result ok "the back buffer is not the screen" "row one showed, the other screen did not" \
|
||||
|| result no "the back buffer is not the screen" "row one $(pixel backbuffer 0 8), corner $(pixel backbuffer 0 0)"
|
||||
|
||||
# The same program, and one more byte out of one more port.
|
||||
{ prologue
|
||||
pokeAtlas 0xFC04 0xFF; pokeAtlas 0xFC05 0x00; pokeAtlas 0xFC06 0x00
|
||||
for i in $(seq 0 63); do pokeAtlas $((0x0040 + i)) 0x01; done
|
||||
pokeBack 0x4000 0x01; pokeBack 0x4001 0x00
|
||||
port 0x3C 0x01
|
||||
epilogue
|
||||
} | run flipped || exit 1
|
||||
[ "$(pixel flipped 0 0)" = "255,0,0" ] \
|
||||
&& result ok "and the flip is what shows it" "one write to 0x3C, a whole new screen" \
|
||||
|| result no "and the flip is what shows it" "got $(pixel flipped 0 0)"
|
||||
|
||||
# And back again, which is the half that says the first screen was kept rather than copied
|
||||
# over. A program that flips to draw and flips back must find what it left.
|
||||
{ prologue
|
||||
pokeAtlas 0xFC04 0xFF; pokeAtlas 0xFC05 0x00; pokeAtlas 0xFC06 0x00
|
||||
for i in $(seq 0 63); do pokeAtlas $((0x0040 + i)) 0x01; done
|
||||
pokeScreen $((0x4000 + 256)) 0x01
|
||||
pokeBack 0x4000 0x01
|
||||
port 0x3C 0x01
|
||||
port 0x3C 0x00
|
||||
epilogue
|
||||
} | run flippedback || exit 1
|
||||
[ "$(pixel flippedback 0 8)" = "255,0,0" ] && [ "$(pixel flippedback 0 0)" = "0,0,0" ] \
|
||||
&& result ok "and flipping back finds what was there" "row one kept, the corner still empty" \
|
||||
|| result no "and flipping back finds what was there" "corner $(pixel flippedback 0 0), row one $(pixel flippedback 0 8)"
|
||||
|
||||
# Asked for rather than remembered, like every other register on this device. And a screen
|
||||
# that does not exist is not taken, the same as a mode that does not exist.
|
||||
{ prologue; port 0x3C 0x01; show 0x3C; port 0x3C 0x07; show 0x3C; epilogue
|
||||
} | run whichscreen || exit 1
|
||||
[ "$(said whichscreen)" = "1 1" ] \
|
||||
&& result ok "which screen is shown can be asked" "and screen seven was not taken" \
|
||||
|| result no "which screen is shown can be asked" "got $(said whichscreen)"
|
||||
|
||||
# ---- The console draws where the person is looking ----
|
||||
#
|
||||
# Not into a screen of its own. A game that flipped and then faulted needs the message to
|
||||
# land where somebody can read it, and the console has no way of knowing that happened.
|
||||
{ prologue; port 0x3C 0x01; say "A"; epilogue; } | run textflipped || exit 1
|
||||
[ "$(pixel textflipped 2 1)" = "216,216,216" ] \
|
||||
&& result ok "the console follows the flip" "the letter is on the screen being shown" \
|
||||
|| result no "the console follows the flip" "got $(pixel textflipped 2 1)"
|
||||
|
||||
# And it really went to the other one: flipping back finds the first screen as it was.
|
||||
{ prologue; port 0x3C 0x01; say "A"; port 0x3C 0x00; epilogue; } | run textnotback || exit 1
|
||||
[ "$(pixel textnotback 2 1)" = "0,0,0" ] \
|
||||
&& result ok "and wrote it in that screen only" "screen nought never saw the letter" \
|
||||
|| result no "and wrote it in that screen only" "got $(pixel textnotback 2 1)"
|
||||
|
||||
# ---- Modes ----
|
||||
{ prologue; port 0x31 0x01; epilogue; } | run wide || exit 1
|
||||
[ "$(size wide)" = "640 400" ] \
|
||||
@@ -929,6 +1004,44 @@ LEFT="$(python3 "$ROOT/Tests/periodic.py" "$BUILD/noscratch.ppm" grid)"
|
||||
&& result ok "and clears up when it cannot be kept" "no grid left on the screen" \
|
||||
|| result no "and clears up when it cannot be kept" "the grid is still there"
|
||||
|
||||
# ---- The back buffer, from inside the system ----
|
||||
#
|
||||
# Flip draws a whole screen into the bank nobody is looking at, waits, shows it, waits, and
|
||||
# puts it back. Caught here while it is showing: the map it filled is one tile and one
|
||||
# attribute everywhere, so the picture is a SINGLE COLOUR and counting them says so without
|
||||
# depending on which colour scheme one happens to be.
|
||||
python3 -c "open('$BUILD/flip.keys','wb').write(b'Flip\n' + b'\x00'*3000 + b' ' + b'\x00'*9000)"
|
||||
timeout 30 "$EMU" --fast --cycles 200000000 --keyboard "$BUILD/flip.keys" \
|
||||
--screen "$BUILD/flip.ppm" --disk "$ROOT/Tests/build/disks/cosmos.img" \
|
||||
--ram-disk 2048 "$BUILD/cosmos.bin" > "$BUILD/flip.out" 2>&1 || true
|
||||
FLIPPED="$(python3 -c "
|
||||
d = open('$BUILD/flip.ppm', 'rb').read()
|
||||
px = d[d.index(b'255\n') + 4:]
|
||||
print(len({px[o:o + 3] for o in range(0, len(px), 3)}))
|
||||
" 2>/dev/null || echo 0)"
|
||||
[ "$FLIPPED" = "1" ] \
|
||||
&& result ok "a program shows the other screen" "the whole picture is the one it filled" \
|
||||
|| result no "a program shows the other screen" "$FLIPPED colours, not a filled screen"
|
||||
|
||||
# ---- And the system takes it back ----
|
||||
#
|
||||
# The shell's scrollback, its prompt and every line the person typed are in screen NOUGHT.
|
||||
# A program that exited while showing screen one would hand back a shell drawing perfectly
|
||||
# onto a screen nobody had ever written to - blank, and looking for all the world like the
|
||||
# machine had lost everything. Compared cell by cell against the same session without Flip
|
||||
# in it, which is the same way Grid's restore is checked.
|
||||
python3 -c "open('$BUILD/flipbefore.keys','wb').write(b'dir\n' + b'Say a line to come back to\n' + b'\x00'*200)"
|
||||
python3 -c "open('$BUILD/flipafter.keys','wb').write(b'dir\n' + b'Say a line to come back to\n' + b'Flip\n' + b'\x00'*3000 + b' ' + b'\x00'*3000 + b' ' + b'\x00'*3000)"
|
||||
for phase in flipbefore flipafter; do
|
||||
timeout 30 "$EMU" --fast --cycles 200000000 --keyboard "$BUILD/$phase.keys" \
|
||||
--screen "$BUILD/$phase.ppm" --disk "$ROOT/Tests/build/disks/cosmos.img" \
|
||||
--ram-disk 2048 "$BUILD/cosmos.bin" > "$BUILD/$phase.out" 2>&1 || true
|
||||
done
|
||||
BACK="$(python3 "$ROOT/Tests/samescreen.py" "$BUILD/flipbefore.ppm" "$BUILD/flipafter.ppm")"
|
||||
[ "$BACK" = "yes" ] \
|
||||
&& result ok "and the system puts the screen back" "screen nought, with what was on it" \
|
||||
|| result no "and the system puts the screen back" "$BACK"
|
||||
|
||||
# ---- 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
|
||||
|
||||
Reference in New Issue
Block a user