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:
Anachronaut
2026-09-02 11:42:11 -04:00
co-authored by Claude Opus 5
parent eee95ef0ce
commit a916103a7f
21 changed files with 801 additions and 14 deletions
+35 -2
View File
@@ -622,7 +622,9 @@ Two rather than one because the two halves of a screen are written at completely
| Bank | Address | Holds |
| --- | --- | --- |
| Atlas | 0x0000 - 0x3FFF | Tile memory. 256 tiles of 8 by 8, one byte a pixel, so tile n begins at n times 64. |
| Atlas | 0x4000 - 0xFBFF | Free. |
| Atlas | 0x4000 - 0xBFFF | Free. Two more 16K pages of tiles would fit here. |
| Atlas | 0xC000 - 0xC7FF | The sprite table. 256 entries of eight bytes. |
| Atlas | 0xC800 - 0xFBFF | Free. |
| Atlas | 0xFC00 - 0xFFFF | The palette. 256 entries of four bytes: red, green, blue, and one unused. |
| Screen | 0x0000 - 0x3FFF | Free in a tile mode. |
| Screen | 0x4000 - 0xBFFF | The map. 128 rows of 256 bytes. |
@@ -672,13 +674,44 @@ The base port owns the atlas because tiles have been at 0x0000 since there was a
A palette entry is four bytes for the same reason. Entry n begins at n times four, which is a shift; three bytes would need a multiply.
### Sprites:
Things that move without the map moving. A cell is where it is; something between two cells meant rewriting both of them, and 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.
**A sprite is m by n tiles**, taken in reading order from one index. That is the decision the rest follows from: it needs no pixel format of its own, no second kind of memory, and nothing its art can be that the map could not also show. A 16 by 16 character is four tiles, and a program that wants the same picture in the background just names the same four.
The table is 256 entries of 8 bytes at **0xC000 in the atlas**. Eight so that entry n begins at n times eight, which is a shift - the same reason a palette entry is four bytes.
| Byte | Holds |
| --- | --- |
| 0 | The top left tile. The rest follow it in reading order, wrapping at 255. |
| 1 | Attribute. Its low nibble times sixteen is added to every index in the art, exactly as a cell's is. |
| 2, 3 | X, low byte first, **signed**. |
| 4, 5 | Y, the same. |
| 6 | Size: tiles across in the high nibble, tiles down in the low. |
| 7 | Flags. Bit 0 mirrors it, bit 1 turns it over, bit 2 puts it behind. |
The position is signed and sixteen bits because the larger mode is 640 by 400, so neither axis fits in a byte - and because a sprite has to be able to sit half off the left or the top rather than appearing whole at the edge.
**A pixel of zero is not drawn.** Without that every sprite is a rectangle. It is tested before the attribute is added, so a hole is a property of the art rather than of the colour scheme: a sprite drawn in indices 1 to 15 is transparent in the same places in all sixteen.
The same rule read the other way is what **behind** means. A sprite marked behind draws only where the background pixel was zero, so a thing can walk behind a pillar and in front of the floor in the same frame. One rule, applied to whichever layer is in front.
**A sprite of no width or no height draws nothing**, and that is the off switch: it saves a flag, it is per sprite rather than a global the whole table shares, and it means the table is already off when the machine starts, since the atlas wakes up cleared. Note that this is deliberately the opposite of what a length of zero means to the memory controller. The reason is the same both times - moving no bytes is a useless thing to ask for, so zero was free to mean 64K there, and drawing no sprite is the commonest state in this table, so zero has to mean nothing here.
**All of them are drawn, every frame.** Sprites here cannot flicker. Real machines dropped them per scanline because they had a fixed number of shift registers and a fixed time to fill them; this has a loop. The limit is how many entries the table has, which is a constant a program can count on rather than something that depends on what it happens to be drawing. Where two overlap, **the lower number is in front**.
Sprites are drawn over a bitmap as readily as over a map. A bitmap is what a program draws once and leaves; there is no reason the mode that cannot afford to redraw itself should be the one that cannot have things moving on it.
`Programs/CosmOS/Apps/Sprite.asm` moves one across the shell's own text without writing a byte of the map.
### Cells:
Two bytes. The first says which tile, the second how to colour it.
The low nibble of the second byte is **added to every palette index in the tile, sixteen at a time**. A tile drawn in indices 0 to 15 therefore appears in any of sixteen colour schemes without a second copy of it in tile memory. A tile that wants all 256 colours leaves the nibble at zero and gets them. The addition wraps, because a byte plus a byte is a byte.
The high nibble is reserved and should be left at zero, so that a meaning can be given to it later without changing what already-written programs mean.
The high nibble is reserved and should be left at zero, so that a meaning can be given to it later without changing what already-written programs mean. The meaning waiting for it is a tile page: there is room in the atlas for two more pages of 256, and one bit of that nibble would reach them.
### Registers: