SPRITE & COLLISION CONTROL

u8 in RAM at 0xE100 called SPRITE_CONTROL

76543210
ETFxxxxB

B: RW spritesUseRegBank (0=SpriteRegisterBank#0 (first half), 1=SpriteRegisterBank#1 (second half))
E: RW Enable Collision Detection Hardware (1=Enable, 0=Disable)
Controls what happens to the RAM located in the "collision registers" bank.
0xE000..0xE1FF contains the 1st set of collision registers (read/writable by the CPU when F=0, dynamically being updated/referenced by hardware when F=1)
0xE200..0xE3FF contains the 2nd set of collision registers (read/writable by the CPU when F=1, dynamically being updated/referenced by hardware when F=0)
0xE400..0xE7FF is always freely available to the CPU to use as generic RAM, and is untouched by the hardware.
(might be useful for Copper code, or SRAM bank-switching code)
NOTE: Even if the Collision Detection Hardware is disabled, there are a few bits of data located in the
collision ram, which the sprite hardware will still read while rendering.
Specifically, it provides per-sprite depth information.
If you're not using sprites, then you can use these registers for generic RAM.

F: RW Freeze RegisterSet 0 or 1
The frozen set of registers CANNOT by written to by the hardware. Thus, it is available to the CPU.
The unfrozen set is continually being updated by hardware as scanlines are rendered. It is not available to the CPU.
NOTE: It's important to swap this bit only after VBlank occurs, and before scanline -1 is visited
since after that window of opportunity has passed, the hardware will begin to reset the status
of all sprites' collision-bits in the unfrozen register-set. It will then build collision data
throughout scanlines 0..239, by updating various collision bits as it sees events occur.
The collision data will be considered "complete" on scanline 240. If you freeze the other buffer
at or after that point in time, then you will gain access to the new, VALID collision data.
Swapping register-sets mid-frame, will certainly produce unpredictable results, and may even
cause memory corruption within the collision register address space, if the hardware manages to
perform a read-modify-write operation as you are changing the F bit.

T: RW Team Mode (1=Enable, 0=Disable)
In team mode, sprites must be on different teams in order to be recognized as a collision.
Without team mode, all collisions count.

NOTE: The way to recieve new collision data, representing the previously rendered frame, is to...

- Disable the Collision Detection Hardware (E=0)
- Set F=0
- Write all sprite depths & teams to 0xE000..0xE1FF
- Set F=1
- Write all sprite depths & teams to 0xE200..0xE3FF
- Wait for VBlank (no valid collision data will be available for this frame unless you can get this done entirely within the VBlank)
- Enable the Collision Detection Hardware (E=1)
- Game Loop
- Wait for VBlank
- Toggle the Freeze bit (F=!F)
- move sprites before VBlank ends, in order to maximize validity of next frames' collision data,
and avoid on-screen sprite shearing. Note that you could use the sprite register double buffering
to make this far more reliable.
- if F==0: CPU can read/write the frozen collision registers from 0xE000..0xE1FF, at its' leisure.
- if F==1: CPU can read/write the frozen collision registers from 0xE200..0xE3FF, at its' leisure.

SPRITE BASE PALETTE

u8 in RAM at 0xE105 called SPRITE_BASE_PALETTE

76543210
xBBB0000

B: RW Base Palette Value (the bottom bits will be provided by each sprite's control register)

SPRITE CLIP-RECT CONTROL

u8 in RAM at 0xE109 called SPRITE_CLIPRECT_CONTROL

76543210
EIxxxxAB

E: RW Enable (0=cliprect is ignored, 1=cliprect is functional)
I: RW Inverse (0=remove what's inside the box, 1=keep only what's inside the box)
A: RW X2BIT8 (The 9th (high) bit of the X2 coordinate)
B: RW X1BIT8 (The 9th (high) bit of the X1 coordinate)

SPRITE CLIP-RECT X1

u8 in RAM at 0xE10A called SPRITE_CLIPRECT_X1

76543210
VVVVVVVV

V: RW X1 (The low 8 bits of the X1 coordinate)
NOTE: The high bit is in the sprite cliprect control

SPRITE CLIP-RECT Y1

u8 in RAM at 0xE10B called SPRITE_CLIPRECT_Y1

76543210
VVVVVVVV

V: RW Y1 (The Y1 coordinate)

SPRITE CLIP-RECT X2

u8 in RAM at 0xE10C called SPRITE_CLIPRECT_X2

76543210
VVVVVVVV

V: RW X2 (The low 8 bits of the X2 coordinate)
NOTE: The high bit is in the cliprect control

SPRITE CLIP-RECT Y2

u8 in RAM at 0xE10D called SPRITE_CLIPRECT_Y2

76543210
VVVVVVVV

V: RW Y2 (The Y2 coordinate)

Macro/Inline

typedef struct SpriteCollisionRegisterStruct
{
union
{
struct
{
// 111111
// 5432109876543210
// IIIIIIII3210STDD
//
// I: R. Highest SpriteID that we collided with (always a number that's lower than our own ID, or 0xFF if no collision)
// 0: R. Collided with BG0
// 1: R. Collided with BG1
// 2: R. Collided with BG2
// 3: R. Collided with BG3
// S: RW Collided with Sprite
// T: RW My Team#
// D: RW My Depth (0..3)

u8 mFlags; // IIIIIIII[3210STDD]
u8 mSpriteId; // [IIIIIIII]3210STDD
};
struct
{
u16 mReg; // IIIIIIII 3210STDD
};
};
} SpriteCollisionRegister;

// Produce the 8bit value that can be written to mFlags
#define MAKESPRITECOLLFLAGS( myTeamNumber, myRenderDepth ) \
( \
( \
( \
((u8)(myTeamNumber))&1 \
) << 2 \
) | ( \
( \
((u8)(myRenderDepth))&3 \
) \
) \
)

// Produce the 16bit value that can be written to mReg
#define MAKESPRITECOLLREG( myTeamNumber, myRenderDepth ) \
(u16)MAKESPRITECOLLFLAGS( myTeamNumber, myRenderDepth )

// Some often-used values, as handy defines.
#define FOREGROUND_SPRITE_COLL MAKESPRITECOLL( 0, 3 )
#define BACKGROUND_SPRITE_COLL MAKESPRITECOLL( 0, 0 )

// (Only accessable when VIDEOBANKREG == VB_SpriteCollisionRegisters)
__AT(,,0xe000,) SpriteCollisionRegister gSpriteCollBank[2][256]; // 0xe000

(Only accessable when VIDEOBANKREG == VB_SpriteCollisionRegisters)

const SPRITECOLLISION_BASE


SPRITECOLLISION_BASE = 57344

You can test for a collision with a sprite, by checking for FF in the ID, or by checking for the COLL_COLLIDED_WITH_SPRITE flag. The reason that both pieces of information are offered, is so that a simple bit-check can be combined with BG tests as well, if desired. Actually looking up the ID is considered to be optional additional data that the CPU may or may not be interested in.
Testing COLL_COLLIDED_WITH_SPRITE, or testing the mSpriteId for NO_SPRITE_COLLISION_ID, are both considered equally valid ways to test for a collision / no-collistion situation involving this sprite and another sprite.

const NO_SPRITE_COLLISION_ID


NO_SPRITE_COLLISION_ID = 255

const COLL_COLLIDED_WITH_BG0


COLL_COLLIDED_WITH_BG0 = 128

const COLL_COLLIDED_WITH_BG1


COLL_COLLIDED_WITH_BG1 = 64

const COLL_COLLIDED_WITH_BG2


COLL_COLLIDED_WITH_BG2 = 32

const COLL_COLLIDED_WITH_BG3


COLL_COLLIDED_WITH_BG3 = 16

const COLL_COLLIDED_WITH_SPRITE


COLL_COLLIDED_WITH_SPRITE = 8

const COLL_COLLIDED_WITH_ANY_BG


COLL_COLLIDED_WITH_ANY_BG = (COLL_COLLIDED_WITH_BG0|COLL_COLLIDED_WITH_BG1|COLL_COLLIDED_WITH_BG2|COLL_COLLIDED_WITH_BG3)

const COLL_COLLIDED_WITH_ANYTHING


COLL_COLLIDED_WITH_ANYTHING = (COLL_COLLIDED_WITH_ANY_BG|COLL_COLLIDED_WITH_SPRITE)

const COLL_MY_TEAM_MASK


COLL_MY_TEAM_MASK = 4

const COLL_MY_DEPTH_MASK


COLL_MY_DEPTH_MASK = 3

const COLL_GET_MY_DEPTH(n)

from mFlags
COLL_GET_MY_DEPTH(n) = ((n)&((1<<1)|(1<<0)))

const COLL_GET_MY_TEAM(n)

from mFlags
COLL_GET_MY_TEAM(n) = (((n)>>2)&1)

const COLL_COLLISION_BITS_MASK

All bits that indicate a collision
COLL_COLLISION_BITS_MASK = COLL_COLLIDED_WITH_ANYTHING

Macro/Inline

typedef struct SpriteRegisterStruct
{
union
{
struct
{
// [3] [2] [1] [0]
// PPPPRRRX XXXXXXXX YYYYYYYY IIIIIIII
// P: RW Palette (0..15)
// R: RW Sprite Rotation (0..7)
// I: RW Image Index (0..255)
// X: RW X coordinate (0..511)
// Y: RW Y coordinate (0..255)

u8 mImage; // PPPPRRRX XXXXXXXX YYYYYYYY[IIIIIIII]
u8 mY; // PPPPRRRX XXXXXXXX[YYYYYYYY]IIIIIIII
u8 mX; // PPPPRRRX[XXXXXXXX]YYYYYYYY IIIIIIII
u8 mPaletteRotX; // [PPPPRRRX]XXXXXXXX YYYYYYYY IIIIIIII
};
struct
{
u32 mReg; // PPPPRRRX XXXXXXXX YYYYYYYY IIIIIIIII
};
};
} SpriteRegister;

#define MAKESPRITEREG( x, y, img, pal, rot ) \
( \
( \
( \
((u32)(pal))&0x0F \
) << 28 \
) | ( \
( \
((u32)(rot))&0x07 \
) << 25 \
) | ( \
( \
((u32)(x))&0x1FF \
) << 16 \
) | ( \
( \
((u32)(y))&0xFF \
) << 8 \
) | ( \
( \
((u32)(img))&0xFF \
) \
) \
)

#define OFFSCREEN_SPRITE MAKESPRITEREG(0,240,0,0,0)

/*
000 ... 001 ..S 010 .Y. 011 .YS
0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3
0 X X X 0 X X X X 0 X X 0 X X X X
1 X X 1 X X 1 X X X 1 X X
2 X X X 2 X X 2 X X 2 X X
3 X X 3 X X 3 X X X 3 X X

100 X.. 101 X.S 110 XY. 111 XYS
0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3
0 X X X 0 X X 0 X X 0 X X
1 X X 1 X X 1 X X X 1 X X
2 X X X 2 X X 2 X X 2 X X
3 X X 3 X X X X 3 X X X 3 X X X X

000, 011, 110, 101 (rotate a NORMAL image CW)
100, 111, 010, 001 (rotate a FLIPX'd image CW)
010, 001, 100, 111 (rotate a FLIPY'd image CW) NOTE: Same numbers as a FLIPX'd rotation
*/

// Sprite Rotation Value is 3 bits wide.
// XYS
// X: FlipX 1=FlipX, 0=Normal
// Y: FlipY 1=FlipY, 0=Normal
// S: SwapXY 1=Swap X&Y, 0=Normal
// NOTE: Flips are applied before the potential swap.

// (Only accessable when VIDEOBANKREG == VB_SpriteRegisters)
__AT(,,0xE000,) SpriteRegister gSpriteRegsBank[2][256]; // 0xE000

(Only accessable when VIDEOBANKREG == VB_SpriteRegisters)

const SPRITEREGISTERS_BASE


SPRITEREGISTERS_BASE = 57344

const ROT_FLIPX


ROT_FLIPX = 4

const ROT_FLIPY


ROT_FLIPY = 2

const ROT_SWAPXY


ROT_SWAPXY = 1

enum SpriteRot

NameValueComment
SR_Normal0
SR_FlipX4
SR_FlipY2
SR_NormalRot00
SR_NormalRot903
SR_NormalRot1806
SR_NormalRot2705
SR_FlipXRot04
SR_FlipXRot907
SR_FlipXRot1802
SR_FlipXRot2701
SR_FlipYRot02
SR_FlipYRot901
SR_FlipYRot1804
SR_FlipYRot2707