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
+54
-5
@@ -129,13 +129,17 @@
|
||||
// character is four tiles and a program that wants it in the background too just names the
|
||||
// same four.
|
||||
//
|
||||
// The table is 256 entries of 8 bytes. Eight so that entry n begins at n times eight, which
|
||||
// is a shift - the same no-multiply argument that makes a palette entry four bytes and a map
|
||||
// row a page. It sits above the tiles with the whole of 0x4000 to 0xBFFF still clear beneath
|
||||
// it, which is two more 16K pages of tiles if they are ever wanted.
|
||||
// The table is 256 entries of 16 bytes. Sixteen so that entry n begins at n times sixteen,
|
||||
// which is a shift - the same no-multiply argument that makes a palette entry four bytes and
|
||||
// a map row a page.
|
||||
//
|
||||
// It was eight, and grew when scaling arrived: four bytes for a target size and one for a
|
||||
// depth would not fit beside what was already there. Grown NOW rather than later, because
|
||||
// the cost of moving it is a rebuild of the two programs that use it, and the cost of moving
|
||||
// it once somebody has written a game on top of it is not.
|
||||
#define VIDEO_SPRITE_BASE 0xC000
|
||||
#define VIDEO_SPRITE_COUNT 256
|
||||
#define VIDEO_SPRITE_BYTES 8
|
||||
#define VIDEO_SPRITE_BYTES 16
|
||||
|
||||
// Byte 0 is the top left tile, byte 1 the attribute, which means what a map cell's attribute
|
||||
// means: its low nibble times sixteen is added to every index in the art.
|
||||
@@ -169,6 +173,51 @@
|
||||
// below for what "nothing" means.
|
||||
#define VIDEO_SPRITE_BEHIND 0x04
|
||||
|
||||
// ---- How big to draw it, which is not the same as how big it is ----
|
||||
//
|
||||
// Bytes 8 to 11: a target width and a target height in PIXELS, low byte first. The device
|
||||
// stretches the m by n tiles to fill that, so a program says how big the thing should look
|
||||
// and never works out a ratio.
|
||||
//
|
||||
// A TARGET IN PIXELS RATHER THAN A MULTIPLIER, which is the whole of why this is usable on
|
||||
// a machine with no divide. A billboard at distance d wants to be k/d pixels tall, and that
|
||||
// is a number the program has anyway - out of a lookup table, most likely. A multiplier
|
||||
// would have to be a fixed point fraction, computed by dividing, which is the one thing
|
||||
// this CPU cannot do.
|
||||
//
|
||||
// NOUGHT MEANS NATURAL SIZE, eight times the tile count on that axis. So every sprite
|
||||
// written before scaling existed still means what it meant, and the common case - a thing
|
||||
// drawn at the size it was drawn at - costs nothing to say.
|
||||
#define VIDEO_SPRITE_WIDTH 8
|
||||
#define VIDEO_SPRITE_HEIGHT 10
|
||||
|
||||
// ---- And how far away it is ----
|
||||
//
|
||||
// Byte 12. Nought means no depth test at all, which is what every ordinary sprite wants and
|
||||
// what a cleared table already says.
|
||||
//
|
||||
// Otherwise it is compared against the DEPTH BUFFER below, one column at a time, and the
|
||||
// sprite draws only in the columns it is in front of. That is the thing table order cannot
|
||||
// do: a billboard can be nearer than the wall in one column and further in the next, and no
|
||||
// amount of sorting the table expresses that.
|
||||
#define VIDEO_SPRITE_DEPTH 12
|
||||
// Bytes 13 to 15 are reserved and should be left at nought.
|
||||
|
||||
// ---- The depth buffer ----
|
||||
//
|
||||
// One byte a screen column, written by the program and read by the device. A wall pass says
|
||||
// how far away the thing it drew in each column was; a sprite with a depth says how far away
|
||||
// it is; and a sprite pixel is drawn only where it is nearer.
|
||||
//
|
||||
// NOUGHT IN A COLUMN MEANS NOTHING IS THERE, so a program that never writes this has a
|
||||
// buffer of noughts and every sprite draws - which is the behaviour there was before it
|
||||
// existed. It is not cleared between frames: it belongs to the program, and a program that
|
||||
// draws walls rewrites all of it every frame anyway.
|
||||
//
|
||||
// It is in the atlas because that is where the things a program sets up live, and because
|
||||
// the alternative is a port and this is 640 bytes.
|
||||
#define VIDEO_DEPTH_BASE 0xD000
|
||||
|
||||
// ---- What a sprite does not cover ----
|
||||
//
|
||||
// A PIXEL OF ZERO IS NOT DRAWN. Without that every sprite is a rectangle, and there is no
|
||||
|
||||
Reference in New Issue
Block a user