Give the screen a bitmap mode

V4. Mode 2 is 320 by 200 with a byte a pixel: no tile to look up and no attribute to add,
the byte IS the palette index. Programs/Examples/picture.asm fills a whole one in 127 bytes
of program and 47,498 cycles.

IT IS THE SAME MEMORY AS THE TILES AND THE MAP, which is what shared video memory has always
been, and there is nowhere else it could be - 64,000 bytes of picture in a 65,536 byte bank
leaves room for nothing beside it. Going to bitmap mode does not clear the text screen, it
stops calling it one, and coming back finds the tiles holding whatever the picture put there.
Taking the screen means taking it.

The palette moves to 0xFC00, the top of video memory, because it is the one thing that has to
mean the same in every mode and 64,000 bytes of picture leaves nowhere in the middle for it
to hide. That is a documented address, so the example, the tests and the manual move with it.

A BITMAP HAS NO COLUMNS AND NO ROWS, and both registers read zero rather than a leftover from
the last mode. The console asks: told there is no character screen, it has nowhere to put a
glyph and draws nothing, while still saying everything down the serial line. The honest
alternative is what a machine with shared video memory really does, which is scribble marks
nobody can read across somebody's picture - honest and useless, since a program that has
taken the screen has not stopped wanting to print.

Six checks in Tests/video.sh, to 55: that the mode is 320 by 200, that a byte is one pixel's
colour and only that pixel, that printing leaves a picture alone while the letter still goes
out, and that the columns register says nought and then forty again.

The example is worth reading for one thing beyond the mode: Fill leaves its destination past
what it touched, so two hundred rows are drawn from one address set once. Working out where
row n begins would be n times 320, and this machine has no multiply.

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-29 10:44:15 -04:00
co-authored by Claude Opus 5
parent 1174bd9af5
commit 13b20c8834
10 changed files with 258 additions and 20 deletions
+58 -7
View File
@@ -64,7 +64,7 @@ ASM
# run, so palette entry 0 is the console's paper rather than black. A check that wanted
# black and got paper would be a check that had quietly depended on a default. These
# tests are about the device, so they set what they are about to look at.
poke 0xC000 0x00; poke 0xC001 0x00; poke 0xC002 0x00
poke 0xFC00 0x00; poke 0xFC01 0x00; poke 0xFC02 0x00
}
poke() {
@@ -204,7 +204,7 @@ echo "Checking what the video device draws."
# and column 3 of row 2. A tile drawn one cell out is the commonest way a tile engine is
# wrong, so the check is where it is AND where it is not.
{ prologue
poke 0xC004 0xFF; poke 0xC005 0x00; poke 0xC006 0x00
poke 0xFC04 0xFF; poke 0xFC05 0x00; poke 0xFC06 0x00
for i in $(seq 0 63); do poke $((0x0040 + i)) 0x01; done
poke 0x4000 0x01; poke 0x4001 0x00
poke $((0x4000 + 2 * 256 + 3 * 2)) 0x01
@@ -229,7 +229,7 @@ echo "Checking what the video device draws."
# Same tile, same map, a different palette entry. Nothing about the picture changes except
# the three bytes the colour came from.
{ prologue
poke 0xC004 0x00; poke 0xC005 0xFF; poke 0xC006 0x40
poke 0xFC04 0x00; poke 0xFC05 0xFF; poke 0xFC06 0x40
for i in $(seq 0 63); do poke $((0x0040 + i)) 0x01; done
poke 0x4000 0x01; poke 0x4001 0x00
epilogue
@@ -245,8 +245,8 @@ echo "Checking what the video device draws."
# the only difference between the two cells is the attribute nibble: 0 leaves the index
# alone, 1 adds sixteen. This is the whole of the recolouring feature in one check.
{ prologue
poke 0xC004 0xFF; poke 0xC005 0x00; poke 0xC006 0x00
poke 0xC044 0x00; poke 0xC045 0x00; poke 0xC046 0xFF
poke 0xFC04 0xFF; poke 0xFC05 0x00; poke 0xFC06 0x00
poke 0xFC44 0x00; poke 0xFC45 0x00; poke 0xFC46 0xFF
for i in $(seq 0 63); do poke $((0x0040 + i)) 0x01; done
poke 0x4000 0x01; poke 0x4001 0x00
poke 0x4002 0x01; poke 0x4003 0x01
@@ -266,7 +266,7 @@ echo "Checking what the video device draws."
# row to the top of the screen, which is the whole reason a terminal on this machine is
# affordable at all.
{ prologue
poke 0xC004 0xFF; poke 0xC005 0xFF; poke 0xC006 0x00
poke 0xFC04 0xFF; poke 0xFC05 0xFF; poke 0xFC06 0x00
for i in $(seq 0 63); do poke $((0x0040 + i)) 0x01; done
poke $((0x4000 + 3 * 256)) 0x01
port 0x34 0x03
@@ -285,7 +285,7 @@ echo "Checking what the video device draws."
# Origin 127 with 128 rows puts map row 127 at the top and map row 0 immediately under it.
# A map that clipped instead of wrapping would show nothing on the second row.
{ prologue
poke 0xC004 0xFF; poke 0xC005 0xFF; poke 0xC006 0xFF
poke 0xFC04 0xFF; poke 0xFC05 0xFF; poke 0xFC06 0xFF
for i in $(seq 0 63); do poke $((0x0040 + i)) 0x01; done
poke 0x4000 0x01
port 0x34 0x7F
@@ -535,6 +535,57 @@ coloured blinkagain 0 0 "216,216,216" \
&& result ok "and lit again after that" "which is what blinking is" \
|| result no "and lit again after that" "got $(pixel blinkagain 0 0)"
# ---- A byte a pixel ----
#
# The other kind of screen. No tile to look up and no attribute to add: the byte IS the
# palette index, and it lives over the top of the tiles and the map, because 64,000 bytes of
# picture leaves room for nothing else in a 65,536 byte bank.
# Palette entry 5, then one pixel of it at row 2, column 3 - which is byte 2*320+3 = 643.
{ prologue
poke 0xFC14 0x20; poke 0xFC15 0xC0; poke 0xFC16 0x90
poke 0x0283 0x05
port 0x31 0x02
epilogue
} | run bitmap || exit 1
[ "$(size bitmap)" = "320 200" ] \
&& result ok "bitmap mode is 320 by 200" "$(size bitmap)" \
|| result no "bitmap mode is 320 by 200" "got $(size bitmap)"
coloured bitmap 3 2 "32,192,144" \
&& result ok "and a byte is a pixel's colour" "byte 643 is row 2, column 3" \
|| result no "and a byte is a pixel's colour" "got $(pixel bitmap 3 2)"
coloured bitmap 4 2 "0,0,0" \
&& result ok "and only that pixel" "the one beside it is untouched" \
|| result no "and only that pixel" "got $(pixel bitmap 4 2)"
# ---- And the console keeps off it ----
#
# There is no character screen in bitmap mode, so there is nowhere to put a glyph. The
# alternative is what a machine with shared video memory really does, which is scribble on
# somebody's picture with marks nobody can read. It still says everything down the serial
# line, which is where it was going as well.
{ prologue
poke 0xFC14 0x20; poke 0xFC15 0xC0; poke 0xFC16 0x90
poke 0x0283 0x05
port 0x31 0x02
say "A"
epilogue
} | run bitmaptext || exit 1
coloured bitmaptext 3 2 "32,192,144" \
&& result ok "printing does not touch a bitmap" "the pixel survived a letter" \
|| result no "printing does not touch a bitmap" "got $(pixel bitmaptext 3 2)"
[ "$(said bitmaptext)" = "65" ] \
&& result ok "and the letter still goes out" "down the serial line" \
|| result no "and the letter still goes out" "got $(said bitmaptext)"
# Asking how many columns there are in bitmap mode is asking about something that is not
# there, and nought is the true answer rather than a leftover from the last mode.
{ prologue; port 0x31 0x02; show 0x32; port 0x31 0x00; show 0x32; epilogue
} | run bitmapsize || exit 1
[ "$(said bitmapsize)" = "0 40" ] \
&& result ok "a bitmap has no columns" "and forty again when it is text" \
|| result no "a bitmap has no columns" "got $(said bitmapsize)"
# ---- The frame, which is the only beat this machine has ----
#
# There is no clock. Every program that wanted to happen at a certain speed has until now