|
|
|
@@ -56,7 +56,13 @@ start:
|
|
|
|
|
INIA 0x30
|
|
|
|
|
OUTA 0xE2
|
|
|
|
|
INIA 0x03
|
|
|
|
|
OUTA 0xE8 ; Video memory becomes bank 3
|
|
|
|
|
OUTA 0xE8 ; The atlas - tiles and palette - becomes bank 3
|
|
|
|
|
INIA 0d4
|
|
|
|
|
OUTA 0xE3
|
|
|
|
|
INIA 0x3A
|
|
|
|
|
OUTA 0xE2
|
|
|
|
|
INIA 0x03
|
|
|
|
|
OUTA 0xE8 ; And the screen - the map, or a bitmap - becomes bank 4
|
|
|
|
|
ASM
|
|
|
|
|
# ---- Said rather than assumed ----
|
|
|
|
|
#
|
|
|
|
@@ -64,20 +70,30 @@ 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 0xFC00 0x00; poke 0xFC01 0x00; poke 0xFC02 0x00
|
|
|
|
|
pokeAtlas 0xFC00 0x00; pokeAtlas 0xFC01 0x00; pokeAtlas 0xFC02 0x00
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
poke() {
|
|
|
|
|
# poke <address> <byte>
|
|
|
|
|
printf ' INIA 0x%02X\n OUTA 0xE4\n INIA 0x%02X\n OUTA 0xE5\n INIA 0x%02X\n OUTA 0xE9\n' \
|
|
|
|
|
$(( ($1 >> 8) & 0xFF )) $(( $1 & 0xFF )) $(( $2 & 0xFF ))
|
|
|
|
|
# ---- Which memory, said and not guessed ----
|
|
|
|
|
#
|
|
|
|
|
# The screen is two banks, and an address alone cannot say which one it means: tile 5 and
|
|
|
|
|
# bitmap pixel 5 are both address 0x0005. So every write below names the memory it is for,
|
|
|
|
|
# and there is deliberately no bare poke that picks by address - a helper that guessed would
|
|
|
|
|
# be right for the tiles and wrong for a picture, silently.
|
|
|
|
|
pokeTo() {
|
|
|
|
|
# pokeTo <bank> <address> <byte>
|
|
|
|
|
printf ' INIA 0d%d\n OUTA 0xE3\n INIA 0x%02X\n OUTA 0xE4\n INIA 0x%02X\n OUTA 0xE5\n INIA 0x%02X\n OUTA 0xE9\n' \
|
|
|
|
|
"$1" $(( ($2 >> 8) & 0xFF )) $(( $2 & 0xFF )) $(( $3 & 0xFF ))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pokeAtlas() { pokeTo 3 "$1" "$2"; } # Tiles and the palette.
|
|
|
|
|
pokeScreen() { pokeTo 4 "$1" "$2"; } # The map, or a bitmap.
|
|
|
|
|
|
|
|
|
|
# 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.
|
|
|
|
|
pokeRun() {
|
|
|
|
|
# pokeRun <address> <byte> <count>
|
|
|
|
|
printf ' INIA 0x%02X\n OUTA 0xE4\n INIA 0x%02X\n OUTA 0xE5\n INIA 0x%02X\n' \
|
|
|
|
|
# address again for each. What a run of tile memory is for, and so far only the atlas needs
|
|
|
|
|
# one.
|
|
|
|
|
pokeAtlasRun() {
|
|
|
|
|
# pokeAtlasRun <address> <byte> <count>
|
|
|
|
|
printf ' INIA 0d3\n OUTA 0xE3\n INIA 0x%02X\n OUTA 0xE4\n INIA 0x%02X\n OUTA 0xE5\n INIA 0x%02X\n' \
|
|
|
|
|
$(( ($1 >> 8) & 0xFF )) $(( $1 & 0xFF )) $(( $2 & 0xFF ))
|
|
|
|
|
local i
|
|
|
|
|
for (( i = 0; i < $3; i++ )); do printf ' OUTA 0xE9\n'; done
|
|
|
|
@@ -214,10 +230,10 @@ 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 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
|
|
|
|
|
pokeAtlas 0xFC04 0xFF; pokeAtlas 0xFC05 0x00; pokeAtlas 0xFC06 0x00
|
|
|
|
|
for i in $(seq 0 63); do pokeAtlas $((0x0040 + i)) 0x01; done
|
|
|
|
|
pokeScreen 0x4000 0x01; pokeScreen 0x4001 0x00
|
|
|
|
|
pokeScreen $((0x4000 + 2 * 256 + 3 * 2)) 0x01
|
|
|
|
|
epilogue
|
|
|
|
|
} | run corner || exit 1
|
|
|
|
|
|
|
|
|
@@ -239,9 +255,9 @@ 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 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
|
|
|
|
|
pokeAtlas 0xFC04 0x00; pokeAtlas 0xFC05 0xFF; pokeAtlas 0xFC06 0x40
|
|
|
|
|
for i in $(seq 0 63); do pokeAtlas $((0x0040 + i)) 0x01; done
|
|
|
|
|
pokeScreen 0x4000 0x01; pokeScreen 0x4001 0x00
|
|
|
|
|
epilogue
|
|
|
|
|
} | run palette || exit 1
|
|
|
|
|
|
|
|
|
@@ -255,11 +271,11 @@ 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 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
|
|
|
|
|
pokeAtlas 0xFC04 0xFF; pokeAtlas 0xFC05 0x00; pokeAtlas 0xFC06 0x00
|
|
|
|
|
pokeAtlas 0xFC44 0x00; pokeAtlas 0xFC45 0x00; pokeAtlas 0xFC46 0xFF
|
|
|
|
|
for i in $(seq 0 63); do pokeAtlas $((0x0040 + i)) 0x01; done
|
|
|
|
|
pokeScreen 0x4000 0x01; pokeScreen 0x4001 0x00
|
|
|
|
|
pokeScreen 0x4002 0x01; pokeScreen 0x4003 0x01
|
|
|
|
|
epilogue
|
|
|
|
|
} | run attribute || exit 1
|
|
|
|
|
|
|
|
|
@@ -276,9 +292,9 @@ 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 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
|
|
|
|
|
pokeAtlas 0xFC04 0xFF; pokeAtlas 0xFC05 0xFF; pokeAtlas 0xFC06 0x00
|
|
|
|
|
for i in $(seq 0 63); do pokeAtlas $((0x0040 + i)) 0x01; done
|
|
|
|
|
pokeScreen $((0x4000 + 3 * 256)) 0x01
|
|
|
|
|
port 0x34 0x03
|
|
|
|
|
epilogue
|
|
|
|
|
} | run scroll || exit 1
|
|
|
|
@@ -295,9 +311,9 @@ 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 0xFC04 0xFF; poke 0xFC05 0xFF; poke 0xFC06 0xFF
|
|
|
|
|
for i in $(seq 0 63); do poke $((0x0040 + i)) 0x01; done
|
|
|
|
|
poke 0x4000 0x01
|
|
|
|
|
pokeAtlas 0xFC04 0xFF; pokeAtlas 0xFC05 0xFF; pokeAtlas 0xFC06 0xFF
|
|
|
|
|
for i in $(seq 0 63); do pokeAtlas $((0x0040 + i)) 0x01; done
|
|
|
|
|
pokeScreen 0x4000 0x01
|
|
|
|
|
port 0x34 0x7F
|
|
|
|
|
epilogue
|
|
|
|
|
} | run ring || exit 1
|
|
|
|
@@ -553,8 +569,8 @@ coloured blinkagain 0 0 "216,216,216" \
|
|
|
|
|
|
|
|
|
|
# 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
|
|
|
|
|
pokeAtlas 0xFC14 0x20; pokeAtlas 0xFC15 0xC0; pokeAtlas 0xFC16 0x90
|
|
|
|
|
pokeScreen 0x0283 0x05
|
|
|
|
|
port 0x31 0x02
|
|
|
|
|
epilogue
|
|
|
|
|
} | run bitmap || exit 1
|
|
|
|
@@ -575,8 +591,8 @@ coloured bitmap 4 2 "0,0,0" \
|
|
|
|
|
# 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
|
|
|
|
|
pokeAtlas 0xFC14 0x20; pokeAtlas 0xFC15 0xC0; pokeAtlas 0xFC16 0x90
|
|
|
|
|
pokeScreen 0x0283 0x05
|
|
|
|
|
port 0x31 0x02
|
|
|
|
|
say "A"
|
|
|
|
|
epilogue
|
|
|
|
@@ -596,6 +612,35 @@ coloured bitmaptext 3 2 "32,192,144" \
|
|
|
|
|
&& 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 example that draws one, run as it ships ----
|
|
|
|
|
#
|
|
|
|
|
# Everything above builds its program here, which means every check above passes on an
|
|
|
|
|
# emulator whose two banks are wired up EXACTLY the way this file assumes. picture.asm is the
|
|
|
|
|
# thing somebody reads to learn how to draw, and nothing ran it.
|
|
|
|
|
#
|
|
|
|
|
# That is not hypothetical. Splitting video memory into two banks broke this program and no
|
|
|
|
|
# check noticed, because registering the second bank leaves DestBank pointing at it - so the
|
|
|
|
|
# palette went into the screen instead of the atlas and the picture came out black.
|
|
|
|
|
#
|
|
|
|
|
# What is checked is the gradient the program's own comment promises: two hundred rows, each
|
|
|
|
|
# one colour, running blue to white to yellow. A blank screen has one colour and a picture
|
|
|
|
|
# drawn with the wrong palette has a handful, so counting them catches both.
|
|
|
|
|
"$ASM" -I "$ROOT/Programs/Libraries" "$ROOT/Programs/Examples/picture.asm" \
|
|
|
|
|
-o "$BUILD/picture.bin" > "$BUILD/picture.log" 2>&1
|
|
|
|
|
timeout 30 "$EMU" --fast --cycles 5000000 --screen "$BUILD/picture.ppm" \
|
|
|
|
|
"$BUILD/picture.bin" > "$BUILD/picture.out" 2>&1 || true
|
|
|
|
|
SHADES="$(python3 -c "
|
|
|
|
|
d = open('$BUILD/picture.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)"
|
|
|
|
|
[ "$SHADES" = "200" ] \
|
|
|
|
|
&& result ok "the example draws its picture" "two hundred rows, two hundred colours" \
|
|
|
|
|
|| result no "the example draws its picture" "$SHADES colours, not 200"
|
|
|
|
|
[ "$(pixel picture 10 0)" = "0,0,255" ] && [ "$(pixel picture 10 199)" = "199,199,56" ] \
|
|
|
|
|
&& result ok "and it runs blue to yellow" "the palette is in the atlas, where it belongs" \
|
|
|
|
|
|| result no "and it runs blue to yellow" "top $(pixel picture 10 0), bottom $(pixel picture 10 199)"
|
|
|
|
|
|
|
|
|
|
# ---- 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
|
|
|
|
@@ -735,9 +780,9 @@ TOTAL="$(grep -oE 'after [0-9]+' "$BUILD/poller.out" | grep -oE '[0-9]+')"
|
|
|
|
|
# what is compared is where the red stops.
|
|
|
|
|
scrollSetup() {
|
|
|
|
|
prologue
|
|
|
|
|
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
|
|
|
|
|
pokeAtlas 0xFC04 0xFF; pokeAtlas 0xFC05 0x00; pokeAtlas 0xFC06 0x00
|
|
|
|
|
for i in $(seq 0 63); do pokeAtlas $((0x0040 + i)) 0x01; done
|
|
|
|
|
pokeScreen 0x4000 0x01; pokeScreen 0x4001 0x00
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Where it is with nothing scrolled: the red runs from 0 to 7 and stops.
|
|
|
|
@@ -767,14 +812,14 @@ scrollSetup() {
|
|
|
|
|
|
|
|
|
|
# Coarse X moves a whole cell. With the column origin at 1 the corner cell is off the left
|
|
|
|
|
# and cell 1 of the map is where the screen starts - so the corner is no longer red.
|
|
|
|
|
{ scrollSetup; poke $((0x4000 + 2)) 0x01; port 0x36 0x01; epilogue; } | run scrollcx || exit 1
|
|
|
|
|
{ scrollSetup; pokeScreen $((0x4000 + 2)) 0x01; port 0x36 0x01; epilogue; } | run scrollcx || exit 1
|
|
|
|
|
[ "$(pixel scrollcx 0 0)" = "255,0,0" ] && [ "$(pixel scrollcx 8 0)" != "255,0,0" ] \
|
|
|
|
|
&& result ok "coarse X moves a whole cell" "the map moved one cell left" \
|
|
|
|
|
|| result no "coarse X moves a whole cell" "0 is $(pixel scrollcx 0 0), 8 is $(pixel scrollcx 8 0)"
|
|
|
|
|
|
|
|
|
|
# And it is a ring, the same as the rows are. Column 127 is the last one a map row has, so
|
|
|
|
|
# an origin there puts it on screen with column 0 beside it.
|
|
|
|
|
{ scrollSetup; poke $((0x4000 + 127 * 2)) 0x01; port 0x36 0x7F; epilogue; } | run scrollwrapx || exit 1
|
|
|
|
|
{ scrollSetup; pokeScreen $((0x4000 + 127 * 2)) 0x01; port 0x36 0x7F; epilogue; } | run scrollwrapx || exit 1
|
|
|
|
|
[ "$(pixel scrollwrapx 0 0)" = "255,0,0" ] && [ "$(pixel scrollwrapx 8 0)" = "255,0,0" ] \
|
|
|
|
|
&& result ok "the columns are a ring too" "127 on screen with 0 beside it" \
|
|
|
|
|
|| result no "the columns are a ring too" "0 is $(pixel scrollwrapx 0 0), 8 is $(pixel scrollwrapx 8 0)"
|
|
|
|
@@ -934,7 +979,7 @@ inked scrollback 2 1 \
|
|
|
|
|
# The glyph for 'A' is filled with ink, which makes the cell a solid block, so the top left
|
|
|
|
|
# pixel of it is ink where a real 'A' has paper. That is a pixel no font disagrees about.
|
|
|
|
|
{ prologue
|
|
|
|
|
pokeRun 0x0840 0x01 64 # Tile 33, which is 'A', every pixel ink.
|
|
|
|
|
pokeAtlasRun 0x0840 0x01 64 # Tile 33, which is 'A', every pixel ink.
|
|
|
|
|
say "A"
|
|
|
|
|
epilogue
|
|
|
|
|
} | run fontwrecked || exit 1
|
|
|
|
@@ -943,7 +988,7 @@ inked scrollback 2 1 \
|
|
|
|
|
|| result no "a program can overwrite a glyph" "not ink at 0,0"
|
|
|
|
|
|
|
|
|
|
{ prologue
|
|
|
|
|
pokeRun 0x0840 0x01 64
|
|
|
|
|
pokeAtlasRun 0x0840 0x01 64
|
|
|
|
|
port 0x39 0x01 # And ask the character generator for it back.
|
|
|
|
|
say "A"
|
|
|
|
|
epilogue
|
|
|
|
@@ -959,9 +1004,9 @@ inked scrollback 2 1 \
|
|
|
|
|
# that defined a tile of its own and then wanted its text back would have paid for it with
|
|
|
|
|
# the tile. It writes the glyphs it has and stops.
|
|
|
|
|
{ prologue
|
|
|
|
|
pokeRun 0x3200 0x01 64 # Tile 200, well above anything the font occupies.
|
|
|
|
|
poke 0x4000 0xC8 # And that tile in the first cell of the map.
|
|
|
|
|
poke 0x4001 0x00
|
|
|
|
|
pokeAtlasRun 0x3200 0x01 64 # Tile 200, well above anything the font occupies.
|
|
|
|
|
pokeScreen 0x4000 0xC8 # And that tile in the first cell of the map.
|
|
|
|
|
pokeScreen 0x4001 0x00
|
|
|
|
|
port 0x39 0x03 # Both the font and the palette back.
|
|
|
|
|
epilogue
|
|
|
|
|
} | run fontkeeps || exit 1
|
|
|
|
@@ -975,7 +1020,7 @@ inked scrollback 2 1 \
|
|
|
|
|
# which is exactly what the fault screen has to survive. The device is asked for the sixteen
|
|
|
|
|
# schemes back and the writing returns.
|
|
|
|
|
{ prologue
|
|
|
|
|
poke 0xFC04 0x00; poke 0xFC05 0x00; poke 0xFC06 0x00 # Scheme 0's ink, made black.
|
|
|
|
|
pokeAtlas 0xFC04 0x00; pokeAtlas 0xFC05 0x00; pokeAtlas 0xFC06 0x00 # Scheme 0's ink, made black.
|
|
|
|
|
say "A"
|
|
|
|
|
epilogue
|
|
|
|
|
} | run inkwrecked || exit 1
|
|
|
|
@@ -984,7 +1029,7 @@ inked scrollback 2 1 \
|
|
|
|
|
|| result no "a program can overwrite a scheme" "the A is still visible"
|
|
|
|
|
|
|
|
|
|
{ prologue
|
|
|
|
|
poke 0xFC04 0x00; poke 0xFC05 0x00; poke 0xFC06 0x00
|
|
|
|
|
pokeAtlas 0xFC04 0x00; pokeAtlas 0xFC05 0x00; pokeAtlas 0xFC06 0x00
|
|
|
|
|
port 0x39 0x02 # The schemes back, and only the schemes.
|
|
|
|
|
say "A"
|
|
|
|
|
epilogue
|
|
|
|
|