Sprites that scale, and a depth buffer to hide them behind
A target size in PIXELS rather than a multiplier, which is the whole of why this is usable here. A billboard at distance d wants to be k/d pixels tall, and that is a number a program has anyway - out of a lookup table, most likely. A multiplier would have to be a fixed point fraction arrived at by dividing, and this CPU cannot divide. Zero on an axis means the natural size, so every sprite written before scaling existed still means what it meant. The two axes are independent, and that shape - one tile wide at its own size, stretched to whatever height a distance says - is a wall column in a pseudo-3D game. Measured: a DDA step costs 85 cycles, so 80 columns of ray casting is about 85,000 cycles, or 12fps. Drawing those walls from the CPU instead would be 256,000 writes, fifteen frames of cycles for one frame of screen. The device doing the pixels is what makes such a game possible at all here, not merely faster. And a depth buffer, one byte a screen column at 0xD000, written by the program. A sprite with a depth draws only in the columns it is in front of. PER COLUMN, and that is the point: a billboard is nearer than the wall at one end of itself and further at the other, and no ordering of the table can say that. Table order settles sprites against each other; the buffer settles them against the scenery. Zero means no test at both ends, so a program that never writes it behaves as it did before it existed. The entry grew from 8 bytes to 16 - now, while two programs use the table, rather than once a game is written on it. Bytes 0 to 7 kept their meanings, so Sprite.asm needed no change. The pass is rewritten to walk where a sprite is GOING rather than where it came from, which is what makes a stretch and a squash one operation. It also made flipping fall out: turning the source coordinate round mirrors the tile order and the pixels inside each tile in one step, where drawing tile by tile had to be told to do both. All 111 checks passed unchanged at natural size, which is what says the rewrite changed nothing it should not. Clipping moved out of the inner loop and had to: a target size is sixteen bits, so a sprite asked to be 60,000 pixels tall would have been sixty thousand turns of a loop that drew eight rows. 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
f8c3db5d56
commit
cb898450b5
@@ -624,8 +624,9 @@ Two rather than one because the two halves of a screen are written at completely
|
||||
| Atlas | 0x0000 - 0x3FFF | Tile page 0. 256 tiles of 8 by 8, one byte a pixel, so tile n begins at n times 64. |
|
||||
| Atlas | 0x4000 - 0x7FFF | Tile page 1. |
|
||||
| Atlas | 0x8000 - 0xBFFF | Tile page 2. |
|
||||
| Atlas | 0xC000 - 0xC7FF | The sprite table. 256 entries of eight bytes - and tiles 0 to 31 of page 3. |
|
||||
| Atlas | 0xC800 - 0xFBFF | Free - and tiles 32 to 239 of page 3. |
|
||||
| Atlas | 0xC000 - 0xCFFF | The sprite table. 256 entries of sixteen bytes - and tiles 0 to 63 of page 3. |
|
||||
| Atlas | 0xD000 - 0xD27F | The depth buffer. One byte a screen column - and tiles 64 to 73 of page 3. |
|
||||
| Atlas | 0xD280 - 0xFBFF | Free - and tiles 74 to 239 of page 3. |
|
||||
| Atlas | 0xFC00 - 0xFFFF | The palette. 256 entries of four bytes: red, green, blue, and one unused - and tiles 240 to 255 of page 3. |
|
||||
| Screen | 0x0000 - 0x3FFF | Free in a tile mode. |
|
||||
| Screen | 0x4000 - 0xBFFF | The map. 128 rows of 256 bytes. |
|
||||
@@ -683,7 +684,7 @@ A sprite's attribute is a cell's attribute, read the same way and by the same co
|
||||
|
||||
**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.
|
||||
The table is 256 entries of 16 bytes at **0xC000 in the atlas**. Sixteen so that entry n begins at n times sixteen, which is a shift - the same reason a palette entry is four bytes.
|
||||
|
||||
| Byte | Holds |
|
||||
| --- | --- |
|
||||
@@ -693,6 +694,10 @@ The table is 256 entries of 8 bytes at **0xC000 in the atlas**. Eight so that en
|
||||
| 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. |
|
||||
| 8, 9 | Target width in pixels. Zero means the natural width, eight times the tiles across. |
|
||||
| 10, 11 | Target height, the same. |
|
||||
| 12 | Depth. Zero means no depth test. |
|
||||
| 13 - 15 | Reserved. Leave at zero. |
|
||||
|
||||
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.
|
||||
|
||||
@@ -706,6 +711,24 @@ The same rule read the other way is what **behind** means. A sprite marked behin
|
||||
|
||||
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.
|
||||
|
||||
### Scaling:
|
||||
|
||||
**Bytes 8 to 11 say how big to draw it**, in pixels, and the device stretches the m by n tiles to fill that. Zero on an axis means the natural size, so every sprite written before scaling existed still means what it meant, and a thing drawn at the size it was drawn at costs nothing to say.
|
||||
|
||||
**A target in pixels rather than a multiplier**, which is the whole of why this is usable here. A billboard at distance *d* wants to be *k/d* pixels tall, and that is a number a program has anyway - out of a lookup table, most likely. A multiplier would have to be a fixed-point fraction, arrived at by dividing, and this CPU cannot divide.
|
||||
|
||||
The two axes are independent, so a sprite can be stretched one way and not the other. That shape - one tile wide at its own size, stretched to whatever height a distance says - is a wall column in a pseudo-3D game, and it is the reason such a game is possible at all on this machine. Drawing 640 by 400 pixels of wall from the CPU is 256,000 writes, which is fifteen frames of cycles for one frame of screen. Writing sixteen bytes a column and letting the device do the pixels is about three thousand.
|
||||
|
||||
### Depth:
|
||||
|
||||
**Byte 12 says how far away a sprite is**, and the **depth buffer** at 0xD000 says how far away the scenery is: one byte a screen column, written by the program. A sprite pixel is drawn only in the columns it is in front of.
|
||||
|
||||
Zero in a sprite's depth means no depth test at all, which is what every ordinary sprite wants and what a cleared table already says. Zero in a column means nothing is there, so a program that never writes the buffer has one of zeroes and every sprite draws - which is exactly the behaviour there was before the buffer existed.
|
||||
|
||||
**Per column, and that is the point.** A billboard can be nearer than the wall at one end of itself and further at the other, and no ordering of the sprite table can say that. Table order settles sprites against each other; the buffer settles them against the scenery.
|
||||
|
||||
The buffer belongs to the program. It is not cleared between frames, because a program that draws scenery rewrites all of it every frame anyway.
|
||||
|
||||
`Programs/CosmOS/Apps/Sprite.asm` moves one across the shell's own text without writing a byte of the map.
|
||||
|
||||
### Cells:
|
||||
|
||||
Reference in New Issue
Block a user