Sprites: things that move without the screen moving
Everything drawn on this machine was in a cell. Something between two cells meant rewriting both; something moving a pixel at a time meant rewriting them sixty times a second, which is affordable for one thing and not for twenty. A sprite is put at a pixel and the device draws it over whatever is behind, so moving it costs two bytes. MADE OF TILES, which is the decision the rest follows from: m by n taken in reading order from one index, so there is no second pixel format, no second kind of memory, and nothing a sprite can show that the map cannot. A 16 by 16 character is four tiles and the background can name the same four. 256 entries of 8 bytes at 0xC000 in the atlas - eight so the entry address is a shift, the same no-multiply argument as the palette's four. Position is signed and sixteen bits, because 640 by 400 does not fit in a byte and a sprite has to be able to sit half off the left rather than appearing whole at the edge. A PIXEL OF ZERO IS NOT DRAWN, or every sprite is a rectangle. Tested before the attribute is added, so a hole belongs to the art and not to the colour scheme. The same rule the other way round is what "behind" means: drawn only where the background pixel was zero, so a thing walks behind a pillar and in front of the floor in one frame. All of them draw, every frame, so they cannot flicker. Real machines dropped them per scanline because they had a fixed number of shift registers; this has a loop. The limit is the size of the table, which is a constant rather than a property of what is on screen. And the system takes them down at exit. The sprite table sits in the gap the screen save walks around - to the end of the map, then the palette - and that is right, because nothing the shell draws is a sprite: there is nothing to give back, only something to take away. Otherwise a program that put a ball up and left would leave it over the prompt, in front of everything, with nothing able to type it away. Sprite.asm deliberately leaves its own, because a program that faulted could not have cleared it. Every check here was re-broken and failed: transparency, reading order, draw order, priority, and size. Size needed breaking twice - the first attempt did not compile, and a silent build failure had left the old binary passing. 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
eee95ef0ce
commit
a916103a7f
@@ -23,6 +23,7 @@ Grid.sbx 571
|
||||
Press.sbx 872
|
||||
Mode.sbx 48
|
||||
Flip.sbx 173
|
||||
Sprite.sbx 442
|
||||
Crash.sbx 632
|
||||
vars.script 50
|
||||
blocks.script 343
|
||||
@@ -39,7 +40,7 @@ outer.script 376
|
||||
inner.script 44
|
||||
loop.script 35
|
||||
crossed.txt 560
|
||||
26 files, 1 directory
|
||||
27 files, 1 directory
|
||||
> exit
|
||||
halted
|
||||
Execution halted.
|
||||
|
||||
@@ -13,6 +13,7 @@ Grid.sbx 571
|
||||
Press.sbx 872
|
||||
Mode.sbx 48
|
||||
Flip.sbx 173
|
||||
Sprite.sbx 442
|
||||
Crash.sbx 632
|
||||
vars.script 50
|
||||
blocks.script 343
|
||||
@@ -28,7 +29,7 @@ nonl.script 38
|
||||
outer.script 376
|
||||
inner.script 44
|
||||
loop.script 35
|
||||
25 files, 1 directory
|
||||
26 files, 1 directory
|
||||
> drive 1
|
||||
> dir
|
||||
other.txt 28
|
||||
|
||||
@@ -30,6 +30,7 @@ Grid.sbx 571
|
||||
Press.sbx 872
|
||||
Mode.sbx 48
|
||||
Flip.sbx 173
|
||||
Sprite.sbx 442
|
||||
Crash.sbx 632
|
||||
vars.script 50
|
||||
blocks.script 343
|
||||
@@ -45,7 +46,7 @@ nonl.script 38
|
||||
outer.script 376
|
||||
inner.script 44
|
||||
loop.script 35
|
||||
25 files, 1 directory
|
||||
26 files, 1 directory
|
||||
> exit
|
||||
halted
|
||||
Execution halted.
|
||||
|
||||
@@ -20,6 +20,7 @@ Grid.sbx 571
|
||||
Press.sbx 872
|
||||
Mode.sbx 48
|
||||
Flip.sbx 173
|
||||
Sprite.sbx 442
|
||||
Crash.sbx 632
|
||||
vars.script 50
|
||||
blocks.script 343
|
||||
@@ -35,7 +36,7 @@ nonl.script 38
|
||||
outer.script 376
|
||||
inner.script 44
|
||||
loop.script 35
|
||||
25 files, 1 directory
|
||||
26 files, 1 directory
|
||||
> exit
|
||||
halted
|
||||
Execution halted.
|
||||
|
||||
@@ -20,6 +20,7 @@ Grid.sbx 571
|
||||
Press.sbx 872
|
||||
Mode.sbx 48
|
||||
Flip.sbx 173
|
||||
Sprite.sbx 442
|
||||
Crash.sbx 632
|
||||
vars.script 50
|
||||
blocks.script 343
|
||||
@@ -35,7 +36,7 @@ nonl.script 38
|
||||
outer.script 376
|
||||
inner.script 44
|
||||
loop.script 35
|
||||
25 files, 1 directory
|
||||
26 files, 1 directory
|
||||
> exit
|
||||
halted
|
||||
Execution halted.
|
||||
|
||||
@@ -106,6 +106,7 @@ Grid.sbx 571
|
||||
Press.sbx 872
|
||||
Mode.sbx 48
|
||||
Flip.sbx 173
|
||||
Sprite.sbx 442
|
||||
Crash.sbx 632
|
||||
vars.script 50
|
||||
blocks.script 343
|
||||
@@ -121,7 +122,7 @@ nonl.script 38
|
||||
outer.script 376
|
||||
inner.script 44
|
||||
loop.script 35
|
||||
25 files, 1 directory
|
||||
26 files, 1 directory
|
||||
> exit
|
||||
halted
|
||||
Execution halted.
|
||||
|
||||
@@ -25,6 +25,7 @@ Grid.sbx 571
|
||||
Press.sbx 872
|
||||
Mode.sbx 48
|
||||
Flip.sbx 173
|
||||
Sprite.sbx 442
|
||||
Crash.sbx 632
|
||||
vars.script 50
|
||||
blocks.script 343
|
||||
@@ -40,7 +41,7 @@ nonl.script 38
|
||||
outer.script 376
|
||||
inner.script 44
|
||||
loop.script 35
|
||||
25 files, 1 directory
|
||||
26 files, 1 directory
|
||||
> exit
|
||||
halted
|
||||
Execution halted.
|
||||
|
||||
@@ -13,6 +13,7 @@ Grid.sbx 571
|
||||
Press.sbx 872
|
||||
Mode.sbx 48
|
||||
Flip.sbx 173
|
||||
Sprite.sbx 442
|
||||
Crash.sbx 632
|
||||
vars.script 50
|
||||
blocks.script 343
|
||||
@@ -28,7 +29,7 @@ nonl.script 38
|
||||
outer.script 376
|
||||
inner.script 44
|
||||
loop.script 35
|
||||
25 files, 1 directory
|
||||
26 files, 1 directory
|
||||
> load
|
||||
load what?
|
||||
> load nosuch.sbx
|
||||
|
||||
@@ -11,6 +11,7 @@ Grid.sbx 571
|
||||
Press.sbx 872
|
||||
Mode.sbx 48
|
||||
Flip.sbx 173
|
||||
Sprite.sbx 442
|
||||
Crash.sbx 632
|
||||
vars.script 50
|
||||
blocks.script 343
|
||||
@@ -26,7 +27,7 @@ nonl.script 38
|
||||
outer.script 376
|
||||
inner.script 44
|
||||
loop.script 35
|
||||
25 files, 1 directory
|
||||
26 files, 1 directory
|
||||
> load Say.sbx
|
||||
loaded, starting at 5000
|
||||
> run the disk took its time
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
CosmOS
|
||||
> Say before Sprite
|
||||
it says: before Sprite
|
||||
finished
|
||||
> Sprite
|
||||
A ball, over the shell's own words. Nothing underneath is written to. Press a key.
|
||||
finished
|
||||
> Say after Sprite
|
||||
it says: after Sprite
|
||||
finished
|
||||
> dir
|
||||
greet.sbx 211
|
||||
hello.sbx 53
|
||||
Life.sbx 1396
|
||||
Snake.sbx 2164
|
||||
Keys.sbx 664
|
||||
Say.sbx 156
|
||||
Break.sbx 149
|
||||
Grid.sbx 571
|
||||
Press.sbx 872
|
||||
Mode.sbx 48
|
||||
Flip.sbx 173
|
||||
Sprite.sbx 442
|
||||
Crash.sbx 632
|
||||
vars.script 50
|
||||
blocks.script 343
|
||||
loops.script 272
|
||||
tune.sbx 306
|
||||
notes.txt 21
|
||||
Apps <dir>
|
||||
hi.script 121
|
||||
bad.script 45
|
||||
plain.script 24
|
||||
cross.script 280
|
||||
nonl.script 38
|
||||
outer.script 376
|
||||
inner.script 44
|
||||
loop.script 35
|
||||
26 files, 1 directory
|
||||
> exit
|
||||
halted
|
||||
Execution halted.
|
||||
[exit 0]
|
||||
Binary file not shown.
@@ -130,6 +130,11 @@ 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/Flip.asm" -o "$WORK/Flip.sbx" >/dev/null
|
||||
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Flip.sbx" >/dev/null
|
||||
# Sprite.sbx moves a ball across the shell's own text without writing a byte of the map, and
|
||||
# leaves the sprite in the table on the way out - so it is what says the system clears them.
|
||||
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
|
||||
"$ROOT/Programs/CosmOS/Apps/Sprite.asm" -o "$WORK/Sprite.sbx" >/dev/null
|
||||
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Sprite.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" \
|
||||
|
||||
@@ -977,6 +977,19 @@ cosmosGrid | CosmOS/Source/cosmos.asm | run | cosmosGri
|
||||
# osTakeScreen, which restores the screen as it was BEFORE the line was printed and so wiped
|
||||
# out the one thing it was pointing at. A program that damages nothing should not ask.
|
||||
cosmosFlip | CosmOS/Source/cosmos.asm | run | cosmosFlip.in | 60000000 | disks/cosmos.img
|
||||
# ---- A thing that moves without the screen moving ----
|
||||
#
|
||||
# Sprite draws a ball over the shell's own text and writes not one byte of the map to do it,
|
||||
# which is what a sprite is for. The picture is video.sh's business; what is checked here is
|
||||
# that the words either side of it are untouched - the line printed before it ran and the one
|
||||
# printed after are both still readable, because nothing underneath the ball was ever written.
|
||||
#
|
||||
# A KEYBOARD FIXTURE and not standard input, which is the difference between a test that
|
||||
# runs this program and one that only looks like it does. A null on standard input is a
|
||||
# character like any other, so the first of them satisfied the "has a key arrived" test and
|
||||
# the ball never moved at all - the whole 150 frames of it went past in the shell, reading a
|
||||
# line made of nulls. Through a keyboard a null is silence, which is what makes it a wait.
|
||||
cosmosSprite | CosmOS/Source/cosmos.asm | run | - | 60000000 | disks/cosmos.img | cosmosSprite.keys
|
||||
# Which disk the registers mean. Several disks are one controller with a drive register
|
||||
# rather than several devices, because a port is an immediate byte inside the instruction
|
||||
# that names it and a program cannot compute one. Run with a single disk, so drive 1 is a
|
||||
|
||||
+224
-1
@@ -100,13 +100,26 @@ print(counts.most_common(1)[0][0].hex())
|
||||
" "$1" 2>/dev/null || echo none
|
||||
}
|
||||
|
||||
# How many pixels of one exact colour a picture has. What a sprite is counted by: it is a
|
||||
# shape rather than a screenful, so the commonest colour says nothing about it.
|
||||
countColour() {
|
||||
# countColour <ppm> <rrggbb>
|
||||
python3 -c "
|
||||
import sys
|
||||
d = open(sys.argv[1], 'rb').read()
|
||||
px = d[d.index(b'255\n') + 4:]
|
||||
want = bytes.fromhex(sys.argv[2])
|
||||
print(sum(1 for o in range(0, len(px), 3) if px[o:o + 3] == want))
|
||||
" "$1" "$2" 2>/dev/null || echo -1
|
||||
}
|
||||
|
||||
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.
|
||||
pokeAtlas() { pokeTo 3 "$1" "$2"; } # Tiles, the palette and the sprite table.
|
||||
pokeScreen() { pokeTo 4 "$1" "$2"; } # The map, or a bitmap.
|
||||
pokeBack() { pokeTo 5 "$1" "$2"; } # The same, in the screen not being shown.
|
||||
|
||||
@@ -121,6 +134,48 @@ pokeAtlasRun() {
|
||||
for (( i = 0; i < $3; i++ )); do printf ' OUTA 0xE9\n'; done
|
||||
}
|
||||
|
||||
# One entry of the sprite table, which is eight bytes at 0xC000 plus eight times its number.
|
||||
# X and Y are signed and go in low byte first, so a negative one is written as its two's
|
||||
# complement here rather than being worked out at every call.
|
||||
spriteAt() {
|
||||
# spriteAt <n> <tile> <attribute> <x> <y> <size> <flags>
|
||||
local base=$(( 0xC000 + $1 * 8 ))
|
||||
local x=$(( $4 & 0xFFFF ))
|
||||
local y=$(( $5 & 0xFFFF ))
|
||||
pokeAtlas "$base" "$2"
|
||||
pokeAtlas "$(( base + 1 ))" "$3"
|
||||
pokeAtlas "$(( base + 2 ))" "$(( x & 0xFF ))"
|
||||
pokeAtlas "$(( base + 3 ))" "$(( (x >> 8) & 0xFF ))"
|
||||
pokeAtlas "$(( base + 4 ))" "$(( y & 0xFF ))"
|
||||
pokeAtlas "$(( base + 5 ))" "$(( (y >> 8) & 0xFF ))"
|
||||
pokeAtlas "$(( base + 6 ))" "$6"
|
||||
pokeAtlas "$(( base + 7 ))" "$7"
|
||||
}
|
||||
|
||||
# Every pixel of one tile the same index. Tile n begins at n times 64.
|
||||
solidTile() {
|
||||
# solidTile <tile> <index>
|
||||
pokeAtlasRun "$(( $1 * 64 ))" "$2" 64
|
||||
}
|
||||
|
||||
# The top half one index and the bottom half another, which is how a tile gets a hole in it:
|
||||
# index nought is what a sprite does not draw.
|
||||
halfTile() {
|
||||
# halfTile <tile> <top> <bottom>
|
||||
pokeAtlasRun "$(( $1 * 64 ))" "$2" 32
|
||||
pokeAtlasRun "$(( $1 * 64 + 32 ))" "$3" 32
|
||||
}
|
||||
|
||||
# Four colours to tell tiles apart by, and the pair a background cell in scheme one uses.
|
||||
spriteColours() {
|
||||
pokeAtlas 0xFC04 0xFF; pokeAtlas 0xFC05 0x00; pokeAtlas 0xFC06 0x00 # 1 red
|
||||
pokeAtlas 0xFC08 0x00; pokeAtlas 0xFC09 0xFF; pokeAtlas 0xFC0A 0x00 # 2 green
|
||||
pokeAtlas 0xFC0C 0x00; pokeAtlas 0xFC0D 0x00; pokeAtlas 0xFC0E 0xFF # 3 blue
|
||||
pokeAtlas 0xFC10 0xFF; pokeAtlas 0xFC11 0xFF; pokeAtlas 0xFC12 0x00 # 4 yellow
|
||||
pokeAtlas 0xFC40 0x00; pokeAtlas 0xFC41 0x00; pokeAtlas 0xFC42 0x00 # 16 black paper
|
||||
pokeAtlas 0xFC44 0x00; pokeAtlas 0xFC45 0xFF; pokeAtlas 0xFC46 0xFF # 17 cyan ink
|
||||
}
|
||||
|
||||
port() {
|
||||
# port <port> <byte>
|
||||
printf ' INIA 0x%02X\n OUTA 0x%02X\n' $(( $2 & 0xFF )) $(( $1 & 0xFF ))
|
||||
@@ -344,6 +399,145 @@ 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)"
|
||||
|
||||
# ---- Sprites ----
|
||||
#
|
||||
# A thing put at a PIXEL rather than in a cell. Tile 1 is solid index one, which the palette
|
||||
# above makes red, and the sprite sits exactly over the cell at row 3 column 2 - so where it
|
||||
# is can be checked against where it is not, which is the way a tile engine is usually wrong.
|
||||
{ prologue
|
||||
spriteColours
|
||||
solidTile 1 0x01
|
||||
spriteAt 0 1 0x00 16 24 0x11 0x00
|
||||
epilogue
|
||||
} | run sprite || exit 1
|
||||
[ "$(pixel sprite 16 24)" = "255,0,0" ] && [ "$(pixel sprite 23 31)" = "255,0,0" ] \
|
||||
&& result ok "a sprite lands where it is put" "and fills its whole eight by eight" \
|
||||
|| result no "a sprite lands where it is put" "corner $(pixel sprite 16 24), far $(pixel sprite 23 31)"
|
||||
[ "$(pixel sprite 15 24)" = "0,0,0" ] && [ "$(pixel sprite 24 24)" = "0,0,0" ] \
|
||||
&& result ok "and stops at its own edge" "a pixel either side is background" \
|
||||
|| result no "and stops at its own edge" "left $(pixel sprite 15 24), right $(pixel sprite 24 24)"
|
||||
|
||||
# ---- What it does not cover ----
|
||||
#
|
||||
# Index nought is a hole and not a colour. The tile is solid on top and empty underneath, and
|
||||
# the cell behind it is cyan, so the bottom half of the sprite must show the cell.
|
||||
{ prologue
|
||||
spriteColours
|
||||
halfTile 3 0x01 0x00
|
||||
pokeScreen 0x4304 0x01; pokeScreen 0x4305 0x01 # Row 3, column 2: tile 1 in scheme 1.
|
||||
solidTile 1 0x01
|
||||
spriteAt 0 3 0x00 16 24 0x11 0x00
|
||||
epilogue
|
||||
} | run spritehole || exit 1
|
||||
[ "$(pixel spritehole 16 24)" = "255,0,0" ] && [ "$(pixel spritehole 16 28)" = "0,255,255" ] \
|
||||
&& result ok "a pixel of nought is not drawn" "the cell behind shows through the hole" \
|
||||
|| result no "a pixel of nought is not drawn" "top $(pixel spritehole 16 24), bottom $(pixel spritehole 16 28)"
|
||||
|
||||
# ---- Bigger than a tile ----
|
||||
#
|
||||
# Two by two, so four tiles in reading order from the one named: 4 and 5 across the top, 6 and
|
||||
# 7 underneath. Each is its own colour, which is the only way to catch a sprite that draws all
|
||||
# four in the right places in the wrong order.
|
||||
{ prologue
|
||||
spriteColours
|
||||
solidTile 4 0x01; solidTile 5 0x02; solidTile 6 0x03; solidTile 7 0x04
|
||||
spriteAt 0 4 0x00 16 24 0x22 0x00
|
||||
epilogue
|
||||
} | run spritebig || exit 1
|
||||
[ "$(pixel spritebig 16 24)" = "255,0,0" ] && [ "$(pixel spritebig 24 24)" = "0,255,0" ] \
|
||||
&& [ "$(pixel spritebig 16 32)" = "0,0,255" ] && [ "$(pixel spritebig 24 32)" = "255,255,0" ] \
|
||||
&& result ok "a sprite is m by n tiles" "four of them, in reading order" \
|
||||
|| result no "a sprite is m by n tiles" "$(pixel spritebig 16 24) $(pixel spritebig 24 24) $(pixel spritebig 16 32) $(pixel spritebig 24 32)"
|
||||
|
||||
# Mirrored, which has to move the TILES and not only the pixels inside them - a two tile wide
|
||||
# thing whose halves stayed put would turn inside out rather than round.
|
||||
{ prologue
|
||||
spriteColours
|
||||
solidTile 4 0x01; solidTile 5 0x02; solidTile 6 0x03; solidTile 7 0x04
|
||||
spriteAt 0 4 0x00 16 24 0x22 0x01
|
||||
epilogue
|
||||
} | run spriteflip || exit 1
|
||||
[ "$(pixel spriteflip 16 24)" = "0,255,0" ] && [ "$(pixel spriteflip 24 24)" = "255,0,0" ] \
|
||||
&& result ok "mirroring moves the tiles too" "the right hand tile came out on the left" \
|
||||
|| result no "mirroring moves the tiles too" "$(pixel spriteflip 16 24) $(pixel spriteflip 24 24)"
|
||||
|
||||
# And upside down, the same argument on the other axis.
|
||||
{ prologue
|
||||
spriteColours
|
||||
solidTile 4 0x01; solidTile 5 0x02; solidTile 6 0x03; solidTile 7 0x04
|
||||
spriteAt 0 4 0x00 16 24 0x22 0x02
|
||||
epilogue
|
||||
} | run spriteover || exit 1
|
||||
[ "$(pixel spriteover 16 24)" = "0,0,255" ] && [ "$(pixel spriteover 16 32)" = "255,0,0" ] \
|
||||
&& result ok "and turning it over does as well" "the bottom tile came out on top" \
|
||||
|| result no "and turning it over does as well" "$(pixel spriteover 16 24) $(pixel spriteover 16 32)"
|
||||
|
||||
# ---- Off the edge ----
|
||||
#
|
||||
# The reason the position is signed. Four pixels off the left is half a tile showing; a whole
|
||||
# tile off is nothing at all, and must be nothing rather than a wrapped one at the far side.
|
||||
{ prologue
|
||||
spriteColours
|
||||
solidTile 1 0x01
|
||||
spriteAt 0 1 0x00 -4 24 0x11 0x00
|
||||
spriteAt 1 1 0x00 -8 40 0x11 0x00
|
||||
epilogue
|
||||
} | run spriteedge || exit 1
|
||||
[ "$(pixel spriteedge 0 24)" = "255,0,0" ] && [ "$(pixel spriteedge 4 24)" = "0,0,0" ] \
|
||||
&& result ok "a sprite can sit off the edge" "half of it showing, and half not" \
|
||||
|| result no "a sprite can sit off the edge" "at 0 $(pixel spriteedge 0 24), at 4 $(pixel spriteedge 4 24)"
|
||||
[ "$(pixel spriteedge 0 40)" = "0,0,0" ] && [ "$(pixel spriteedge 312 40)" = "0,0,0" ] \
|
||||
&& result ok "and right off it is gone" "not wrapped round to the other side" \
|
||||
|| result no "and right off it is gone" "left $(pixel spriteedge 0 40), right $(pixel spriteedge 312 40)"
|
||||
|
||||
# ---- In front, and behind ----
|
||||
#
|
||||
# Behind means drawn only where the background had NOTHING - the same rule that makes a
|
||||
# sprite's own nought a hole, read the other way round. The cell is solid on top and empty
|
||||
# underneath, so a sprite behind it shows through the bottom half only.
|
||||
{ prologue
|
||||
spriteColours
|
||||
halfTile 2 0x01 0x00
|
||||
pokeScreen 0x4304 0x02; pokeScreen 0x4305 0x01 # Row 3, column 2: tile 2 in scheme 1.
|
||||
solidTile 1 0x01
|
||||
spriteAt 0 1 0x00 16 24 0x11 0x04
|
||||
epilogue
|
||||
} | run spritebehind || exit 1
|
||||
[ "$(pixel spritebehind 16 24)" = "0,255,255" ] && [ "$(pixel spritebehind 16 28)" = "255,0,0" ] \
|
||||
&& result ok "a sprite can go behind the map" "hidden where the cell had something" \
|
||||
|| result no "a sprite can go behind the map" "top $(pixel spritebehind 16 24), bottom $(pixel spritebehind 16 28)"
|
||||
|
||||
# Where two overlap, the lower number is in front. Both solid, both at the same place, and
|
||||
# the one that wins says which way round the table is read.
|
||||
{ prologue
|
||||
spriteColours
|
||||
solidTile 1 0x01; solidTile 2 0x02
|
||||
spriteAt 0 1 0x00 16 24 0x11 0x00
|
||||
spriteAt 1 2 0x00 16 24 0x11 0x00
|
||||
epilogue
|
||||
} | run spriteorder || exit 1
|
||||
[ "$(pixel spriteorder 16 24)" = "255,0,0" ] \
|
||||
&& result ok "the lower number is in front" "sprite nought covered sprite one" \
|
||||
|| result no "the lower number is in front" "got $(pixel spriteorder 16 24)"
|
||||
|
||||
# ---- Nothing, which is what the table wakes up as ----
|
||||
#
|
||||
# A size of nought either way draws nothing, and that is the off switch. Everything above
|
||||
# would pass on a device that drew every entry regardless, because every other entry in those
|
||||
# tables happens to be zeroed - this is the one that says zero MEANS something.
|
||||
{ prologue
|
||||
spriteColours
|
||||
solidTile 1 0x01
|
||||
spriteAt 0 1 0x00 16 24 0x01 0x00
|
||||
spriteAt 1 1 0x00 40 24 0x10 0x00
|
||||
spriteAt 2 1 0x00 64 24 0x11 0x00
|
||||
epilogue
|
||||
} | run spritenone || exit 1
|
||||
[ "$(pixel spritenone 16 24)" = "0,0,0" ] && [ "$(pixel spritenone 40 24)" = "0,0,0" ] \
|
||||
&& [ "$(pixel spritenone 64 24)" = "255,0,0" ] \
|
||||
&& result ok "no width or no height draws nothing" "and the one beside them still does" \
|
||||
|| result no "no width or no height draws nothing" "$(pixel spritenone 16 24) $(pixel spritenone 40 24) $(pixel spritenone 64 24)"
|
||||
|
||||
# ---- 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
|
||||
@@ -1054,6 +1248,35 @@ timeout 30 "$EMU" --fast --cycles 200000000 --keyboard "$BUILD/flipafter.keys" \
|
||||
&& result ok "and the system puts the screen back" "black again, and not the filled screen" \
|
||||
|| result no "and the system puts the screen back" "commonest colour $(commonest "$BUILD/flipafter.ppm")"
|
||||
|
||||
# ---- A sprite, from inside the system ----
|
||||
#
|
||||
# Sprite moves a ball across the shell's own text and writes NOT ONE BYTE of the map to do
|
||||
# it. The ball is 52 pixels of scheme one's ink, drawn from a tile whose corners are index
|
||||
# nought - so counting that exact colour finds the ball and nothing else, the shell printing
|
||||
# in grey.
|
||||
python3 -c "open('$BUILD/ball.keys','wb').write(b'Sprite\n' + b'\x00'*40000)"
|
||||
timeout 30 "$EMU" --fast --cycles 60000000 --keyboard "$BUILD/ball.keys" \
|
||||
--screen "$BUILD/ball.ppm" --disk "$ROOT/Tests/build/disks/cosmos.img" \
|
||||
--ram-disk 2048 "$BUILD/cosmos.bin" > "$BUILD/ball.out" 2>&1 || true
|
||||
[ "$(countColour "$BUILD/ball.ppm" d04038)" = "52" ] \
|
||||
&& result ok "a program can put a sprite up" "52 pixels of ball, and a round one" \
|
||||
|| result no "a program can put a sprite up" "$(countColour "$BUILD/ball.ppm" d04038) pixels, not 52"
|
||||
|
||||
# ---- And the system takes it down ----
|
||||
#
|
||||
# The sprite table is in the atlas at 0xC000, and the screen save walks the pages either side
|
||||
# of it: to the end of the map, then the palette. So a sprite is not something the system can
|
||||
# GIVE BACK, and Sprite deliberately does not clear its own - a program that faulted could
|
||||
# not have either. What must not happen is a ball left sitting over the prompt, in front of
|
||||
# everything, with nothing able to type it away.
|
||||
python3 -c "open('$BUILD/ballgone.keys','wb').write(b'Sprite\n' + b'\x00'*600 + b' ' + b'\x00'*600)"
|
||||
timeout 30 "$EMU" --fast --cycles 60000000 --keyboard "$BUILD/ballgone.keys" \
|
||||
--screen "$BUILD/ballgone.ppm" --disk "$ROOT/Tests/build/disks/cosmos.img" \
|
||||
--ram-disk 2048 "$BUILD/cosmos.bin" > "$BUILD/ballgone.out" 2>&1 || true
|
||||
[ "$(countColour "$BUILD/ballgone.ppm" d04038)" = "0" ] \
|
||||
&& result ok "and the system takes the sprite down" "not one pixel of it left over the shell" \
|
||||
|| result no "and the system takes the sprite down" "$(countColour "$BUILD/ballgone.ppm" d04038) pixels still there"
|
||||
|
||||
# ---- 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