From 13b20c8834b3b5b02a7236bfabe74a3fca684247 Mon Sep 17 00:00:00 2001 From: Anachronaut Date: Sat, 29 Aug 2026 10:44:15 -0400 Subject: [PATCH] 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 Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW --- Programs/Examples/colours.asm | 6 +- Programs/Examples/picture.asm | 105 +++++++++++++++++++++++++++++++++ Source/Emulator/io.c | 13 ++++ Source/Emulator/video.c | 34 ++++++++++- Source/Emulator/video.h | 32 ++++++++-- SplitBit Programming Manual.md | 16 ++++- SplitBit Test Manual.md | 2 +- Tests/expected/picture.out | 2 + Tests/manifest | 3 + Tests/video.sh | 65 +++++++++++++++++--- 10 files changed, 258 insertions(+), 20 deletions(-) create mode 100644 Programs/Examples/picture.asm create mode 100644 Tests/expected/picture.out diff --git a/Programs/Examples/colours.asm b/Programs/Examples/colours.asm index 1c39bfb..2c1032d 100644 --- a/Programs/Examples/colours.asm +++ b/Programs/Examples/colours.asm @@ -89,9 +89,9 @@ nextBank: INIA 0x03 OUTA 0xE8 ; Command: RegisterBank - ; The palette starts at 0xC000, and entry n is at n times four. Bank 2's ink is entry - ; 2 * 16 + 1, which is 33, and 33 * 4 is 132 - so 0xC084. - INIA 0xC0 + ; The palette sits at the top of video memory, at 0xFC00, and entry n is at n times + ; four. Bank 2's ink is entry 2 * 16 + 1, which is 33, and 33 * 4 is 132 - so 0xFC84. + INIA 0xFC OUTA 0xE4 ; DestHigh INIA 0x84 OUTA 0xE5 ; DestLow diff --git a/Programs/Examples/picture.asm b/Programs/Examples/picture.asm new file mode 100644 index 0000000..92a85fb --- /dev/null +++ b/Programs/Examples/picture.asm @@ -0,0 +1,105 @@ +; picture.asm +; The other kind of screen: a byte a pixel. +; Written by Anachronaut +; +; ---- Two ways to have a screen ---- +; +; A tile mode costs the machine the number of CELLS that changed. Forty by twenty-five is +; two thousand bytes for a whole screen and four bytes for two cells, which is why text on +; this machine is affordable at all. +; +; A bitmap costs it the number of PIXELS. Three hundred and twenty by two hundred is 64,000 +; bytes - four frames of work at a megahertz to replace all of it. So this is not the mode to +; animate a whole screen in; it is the mode to draw a picture in and then leave alone, or to +; change a corner of. +; +; It lives over the top of tile memory and the map, because there is nowhere else for it: the +; bank is 65,536 bytes and the picture is 64,000 of them. 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 is the one thing that means the same in both, which is why it sits at the very +; top, out of the way of everything. + +#Program + +start: + ; Video memory is the screen's, not this program's, so it is reached the way every device's + ; memory is: given a bank number, then written through the memory controller. Banks 0, 1 + ; and 2 belong to the machine, so 3 is the first one software may hand out. + INIA 0d3 + OUTA 0xE3 ; DestBank: the number being given + INIA 0x30 + OUTA 0xE2 ; SourceLow: the port that owns the memory + INIA 0x03 + OUTA 0xE8 ; Command: RegisterBank + + ; ---- Two hundred and fifty six colours ---- + ; + ; Entry n at 0xFC00 plus n times four. Writing the controller's Data port puts a byte at + ; the destination and steps it on, so the whole palette is one address and a loop. + INIA 0xFC + OUTA 0xE4 + RSTA + OUTA 0xE5 + SETD.0 Count + STA.0 ; Still the zero from DestLow above: SETD does not touch A +palette: + LDA.0 + OUTA 0xE9 ; red climbs + LDA.0 + OUTA 0xE9 ; green with it + LDA.0 + INIB 0xFF + XOR + MVQA + OUTA 0xE9 ; and blue falls away, so it runs blue to white to yellow + RSTA + OUTA 0xE9 ; the fourth byte is spare + LDA.0 + INCA + STA.0 + BNA palette ; A comes back to zero after 256 of them + + ; ---- The picture ---- + ; + ; Two hundred rows of three hundred and twenty pixels, each row one colour. FILL LEAVES THE + ; DESTINATION PAST WHAT IT TOUCHED, so the address is set once here and never worked out + ; again - which matters, because working out where row n begins would be n times 320 and + ; this machine has no multiply. + RSTA + OUTA 0xE4 + OUTA 0xE5 ; Dest 0x0000, the top left corner + INIA 0x01 + OUTA 0xE6 + INIA 0x40 + OUTA 0xE7 ; 320 bytes, which is one row + SETD.0 Count + RSTA + STA.0 +rows: + LDA.0 + OUTA 0xE2 ; Fill takes its byte from SourceLow: the row number is the colour + INIA 0x02 + OUTA 0xE8 ; Command: Fill + LDA.0 + INCA + STA.0 + INIB 0d200 + CCF + SUB + BNQ rows + + ; And now show it. Nothing above cared which mode the screen was in - the bytes were + ; already there, waiting to be called a picture. + INIA 0x02 + OUTA 0x31 + HALT + +#Data + +Count: + 0x00 + +#Vectors + Boot start diff --git a/Source/Emulator/io.c b/Source/Emulator/io.c index 582b5b8..08b0cda 100644 --- a/Source/Emulator/io.c +++ b/Source/Emulator/io.c @@ -301,6 +301,19 @@ static void consoleSayCursor(void) { } static void consoleDraw(uint8_t byte) { + // ---- Nowhere to put a glyph ---- + // + // A console is a display controller, and a display controller draws characters on a + // character screen. In bitmap mode there is not one - the memory it would write into is + // somebody's picture - so it draws nothing and says everything down the serial line + // instead, which is where it was always going as well. + // + // The alternative is what a machine with shared video memory really does, which is + // scribble. That is honest and useless: nobody can read the marks and they ruin the + // picture, and a program that has taken the screen has not stopped wanting to print. + if (videoTextRows() == 0) { + return; + } switch (byte) { case '\n': consoleNewLine(); diff --git a/Source/Emulator/video.c b/Source/Emulator/video.c index 611c691..29f2b40 100644 --- a/Source/Emulator/video.c +++ b/Source/Emulator/video.c @@ -28,8 +28,18 @@ static uint8_t pixels[VIDEO_MAX_WIDTH * VIDEO_MAX_HEIGHT * 3]; static int renderedWidth = 0; static int renderedHeight = 0; -static int columnsFor(uint8_t m) { return m == VIDEO_MODE_80x50 ? 80 : 40; } -static int rowsFor(uint8_t m) { return m == VIDEO_MODE_80x50 ? 50 : 25; } +// Zero in bitmap mode, where there are no characters. Everything that draws one checks, so +// this is the single place the answer lives rather than a mode test in each of them. +static int columnsFor(uint8_t m) { + if (m == VIDEO_MODE_BITMAP) return 0; + return m == VIDEO_MODE_80x50 ? 80 : 40; +} +static int rowsFor(uint8_t m) { + if (m == VIDEO_MODE_BITMAP) return 0; + return m == VIDEO_MODE_80x50 ? 50 : 25; +} + +int videoTextRows(void) { return rowsFor(mode); } int videoColumns(void) { return columnsFor(mode); } int videoRows(void) { return rowsFor(mode); } @@ -231,6 +241,26 @@ uint8_t videoRead(uint8_t port) { } void videoRender(void) { + if (mode == VIDEO_MODE_BITMAP) { + // ---- A byte a pixel, and nothing in the way ---- + // + // No tile to look up and no attribute to add: the byte IS the palette index. Which + // is the whole difference between the two kinds of screen - a tile mode costs the + // CPU the number of cells that changed, and this costs it the number of pixels. + const uint8_t *palette = videoRAM + VIDEO_PALETTE_BASE; + const uint8_t *from = videoRAM + VIDEO_BITMAP_BASE; + uint8_t *out = pixels; + for (int at = 0; at < VIDEO_BITMAP_WIDTH * VIDEO_BITMAP_HEIGHT; at++) { + const uint8_t *entry = palette + from[at] * VIDEO_PALETTE_BYTES; + *out++ = entry[0]; + *out++ = entry[1]; + *out++ = entry[2]; + } + renderedWidth = VIDEO_BITMAP_WIDTH; + renderedHeight = VIDEO_BITMAP_HEIGHT; + return; + } + const int columns = columnsFor(mode); const int rows = rowsFor(mode); const int width = columns * VIDEO_CELL_PIXELS; diff --git a/Source/Emulator/video.h b/Source/Emulator/video.h index c7a334b..973f220 100644 --- a/Source/Emulator/video.h +++ b/Source/Emulator/video.h @@ -51,12 +51,30 @@ // Two bytes to a cell: which tile, and how to colour it. #define VIDEO_CELL_BYTES 2 +// ---- A bitmap, over the top of the tiles and the map ---- +// +// THE SAME MEMORY MEANING DIFFERENT THINGS IN DIFFERENT MODES, which is what shared video +// memory has always been. There is no room for it to be anywhere else: 320 by 200 at a byte +// a pixel is 64,000 bytes and the whole bank is 65,536, so a bitmap that sat beside the +// tiles rather than on top of them would need a second bank for no reason except tidiness. +// +// What it costs is that the two do not coexist. Going to bitmap mode does not clear the text +// screen; it stops calling it a text screen. Coming back finds the tiles and the map holding +// whatever the picture put there, which is what taking the screen means. +#define VIDEO_BITMAP_BASE 0x0000 +#define VIDEO_BITMAP_WIDTH 320 +#define VIDEO_BITMAP_HEIGHT 200 + // ---- The palette ---- // // Four bytes an entry rather than three, for the same reason a map row is a page: entry n // begins at n times four, which is a shift. Three would need a multiply the machine does // not have. The fourth byte is unused and reads as whatever was put there. -#define VIDEO_PALETTE_BASE 0xC000 +// +// At the TOP of video memory, clear of everything else, because it is the one thing that has +// to mean the same in every mode - a bitmap needs colours as much as a tile does, and 64,000 +// bytes of picture leaves nowhere in the middle for it to hide. +#define VIDEO_PALETTE_BASE 0xFC00 #define VIDEO_PALETTE_BYTES 4 #define VIDEO_PALETTE_SIZE 256 @@ -65,14 +83,20 @@ // Both are 8x8 cells over the same engine; only how many of them differ. The pixel count // costs the CPU nothing, because it only ever writes the map - which is why the larger mode // is affordable at all. -#define VIDEO_MODE_40x25 0 -#define VIDEO_MODE_80x50 1 -#define VIDEO_MODE_COUNT 2 +#define VIDEO_MODE_40x25 0 +#define VIDEO_MODE_80x50 1 +#define VIDEO_MODE_BITMAP 2 +#define VIDEO_MODE_COUNT 3 #define VIDEO_CELL_PIXELS 8 #define VIDEO_MAX_WIDTH (80 * VIDEO_CELL_PIXELS) #define VIDEO_MAX_HEIGHT (50 * VIDEO_CELL_PIXELS) +// How many characters across and down the screen is, and ZERO IN BITMAP MODE, where there is +// no such thing. The console asks, and a console told there are no columns has nowhere to +// put a glyph and does not try. +int videoTextRows(void); + // ---- Ports ---- // // Sixteen, like the controller, and it interrupts on its base the way the disk established. diff --git a/SplitBit Programming Manual.md b/SplitBit Programming Manual.md index db8a43e..b949d9b 100644 --- a/SplitBit Programming Manual.md +++ b/SplitBit Programming Manual.md @@ -532,7 +532,12 @@ One bank, brought by the device and reached only through the memory controller, | --- | --- | | 0x0000 - 0x3FFF | Tile memory. 256 tiles of 8 by 8, one byte a pixel, so tile n begins at n times 64. | | 0x4000 - 0xBFFF | The map. 128 rows of 256 bytes. | -| 0xC000 - 0xC3FF | The palette. 256 entries of four bytes: red, green, blue, and one unused. | +| 0x0000 - 0xF9FF | In bitmap mode, the picture instead: 64,000 bytes, one to a pixel. | +| 0xFC00 - 0xFFFF | The palette. 256 entries of four bytes: red, green, blue, and one unused. | + +**The bitmap 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 and the map holding whatever the picture put there. + +The palette is at the top, out of the way of both, because it is the one thing that means the same in every mode. **A map row is a page whether the mode fills it or not**, and that is arithmetic rather than waste. This machine has no multiply, so on a 40 column screen every cursor move would otherwise cost a `row times 40` in software - a tax on the most common operation in the system. At a page a row there is no arithmetic at all: the row number is the high byte of the address and the doubled column is the low byte. @@ -561,8 +566,13 @@ The high nibble is reserved and should be left at zero, so that a meaning can be | --- | --- | --- | | 0 | 320 by 200 | 40 by 25 | | 1 | 640 by 400 | 80 by 50 | +| 2 | 320 by 200 | none: a byte a pixel | -Both are 8 by 8 cells over the same engine, and the pixel count costs a program nothing, because it only ever writes the map. A mode that does not exist is not taken, and is not a fault either: a screen is a poor place to stop the machine, and a program that asked for something impossible still has the screen it had. +The first two are 8 by 8 cells over the same engine, and the pixel count costs a program nothing, because it only ever writes the map. + +**Mode 2 is the other kind of screen**, where a byte is a palette index and there is no tile to look it up in and no attribute to add. What it costs is the other way round: a whole picture is 64,000 bytes, four frames of work at a megahertz, so it is the mode to draw in and leave alone or to change a corner of, not the mode to animate all of. `Programs/Examples/picture.asm` fills one in 127 bytes of program. + +**A bitmap has no columns and no rows**, and asking says so: both registers read zero, which is the true answer rather than a leftover from the last mode. The console asks, and a console told there is no character screen has nowhere to put a glyph and draws nothing - it still says everything down the serial line. The alternative is what a machine with shared video memory really does, which is scribble marks nobody can read across somebody's picture. A mode that does not exist is not taken, and is not a fault either: a screen is a poor place to stop the machine, and a program that asked for something impossible still has the screen it had. How big the screen is, is asked for rather than assumed. A program written once can find out what it is running on. @@ -634,7 +644,7 @@ So `attribute XOR 8` turns any pair inside out, which is what a highlighted line **That arrangement is a convention rather than a rule of the machine.** A program that wants different colours writes its own palette, and one that wants thirty-two of something rather than sixteen pairs can have that too - the device only ever adds the nibble and looks the answer up. -The palette lives at 0xC000 in video memory, four bytes an entry - red, green, blue, and one spare - so entry *n* begins at 0xC000 plus *n* times four. Video memory belongs to the screen rather than to the program, so it is written the way every device's memory is written: registered as a bank, and reached through the memory controller. +The palette lives at 0xFC00 in video memory, four bytes an entry - red, green, blue, and one spare - so entry *n* begins at 0xFC00 plus *n* times four. Video memory belongs to the screen rather than to the program, so it is written the way every device's memory is written: registered as a bank, and reached through the memory controller. `Programs/Examples/colours.asm` does all of that in eighty lines and prints the result. It shows the sixteen pairs, shows what XOR 8 does to each, and then changes one of them by writing three bytes into the palette, so that the difference between using the colours a machine wakes up with and choosing your own is visible in one program. diff --git a/SplitBit Test Manual.md b/SplitBit Test Manual.md index b4d7b09..9196615 100644 --- a/SplitBit Test Manual.md +++ b/SplitBit Test Manual.md @@ -78,7 +78,7 @@ from `make`, not from here. ### 1. Recorded output `Tests/run.sh` assembles each program named in `Tests/manifest`, runs it, and compares -everything it printed against a file in `Tests/expected`. 168 tests, of which 106 run, 35 +everything it printed against a file in `Tests/expected`. 169 tests, of which 107 run, 35 only assemble, 16 are expected to fail to assemble, and 11 boot from ROM with no image given at all. diff --git a/Tests/expected/picture.out b/Tests/expected/picture.out new file mode 100644 index 0000000..6062395 --- /dev/null +++ b/Tests/expected/picture.out @@ -0,0 +1,2 @@ +Execution halted. +[exit 0] diff --git a/Tests/manifest b/Tests/manifest index 5e72e37..25fc346 100644 --- a/Tests/manifest +++ b/Tests/manifest @@ -49,6 +49,9 @@ colours | Examples/colours.asm | run | - # here because the count is exact: a frame is counted in the machine's own cycles, so the # same program sees the same sixty however fast the host really went. frames | Examples/frames.asm | run | - | - +# picture draws a whole 320 by 200 bitmap and prints nothing, so what is recorded is only +# that it ran and what it cost. Tests/video.sh is where the pixels are checked. +picture | Examples/picture.asm | run | - | - 8bitSieve | Examples/primeSieve/8bitSieve.asm | run | - | - 16bitSegmentedSieve | Examples/primeSieve/16bitSieve.asm | run | - | - # The four pointer rewrite. It emits exactly the same primes as the line above, which diff --git a/Tests/video.sh b/Tests/video.sh index 43928a6..e2ebe7d 100755 --- a/Tests/video.sh +++ b/Tests/video.sh @@ -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