
;
; SPRITE PACKAGE
; Alvin Albrecht, aralbrec@interchange.ubc.ca 10/15/2002
;
; Tab stops every 8 chars.
; See "TEST" for an example of how to use sprites.
; Once assembled, a RANDOMIZE USR 59392 will run "TEST".
;
; For ts2068/tc2048 in 512x192 mode, 64x24 chars.
; For quickest frame rates:
; - Use background tiles in preference to sprites.
;   Moving background tiles are at least 9 times faster
;   than moving sprites and at least 6 times faster than
;   moving "fast" sprites.
; - Always keep in mind that this software only updates
;   portions of the screen that have changed.  A very
;   fast scrolling background can be achieved if you are
;   careful.  For example: a horizontal line 8 chars in
;   length can be scrolled horizontally the slow way by
;   "PrintAt"ing 8 line tiles followed by a space or it can
;   be done the fast way by "PrintAt"ing one line tile at
;   the new front position of the line and "PrintAt"ing one
;   clear tile at the end of the line.  That's 4.5 times
;   faster than the slow approach in this case.
; - Use "fast" sprites whenever possible.  Fast sprites are
;   sprites that do not need any horizontal rotation.  They
;   are drawn at an exact column coordinate and use a faster
;   drawchar subroutine resulting in approximately  50% faster
;   performance.  There is no distinction between a fast sprite
;   and a regular sprite: sprites are automatically drawn using
;   this faster method whenever no horizontal rotation is needed.  
;   But you can choose to use fast sprites exclusively by moving
;   to exact horizontal coordinates only.  To gain pixel precision,
;   have several sprite definitions in memory, one for each horizontal
;   rotation desired and use the animate parameter in the move sprite
;   subroutines to substitute one sprite graphic for another.
; - Sprites that do not change char coordinate but are animated and/
;   or are shifted horizontally or vertically by less than 8 pixels
;   are around 20% faster than a sprite that does change char coordinate.
;   This might be useful in a scrolling game where the central character
;   stays in one position while the rest of the background scrolls.
; - If exclusive use of fast sprites is not practical, try to
;   stagger all existing sprites so that a steady portion of them
;   are on an exact column all the time.  This will avoid jerkiness
;   in the screen update caused by an unequal amount of time needed
;   to draw successive updated screens. (Jerkiness is eliminated by
;   modifying "updaterate".  Doing things this way allows updaterate
;   to be improved.)
; - Use the "lastframecnt" variable to determine how many pixels
;   sprites should move in the next frame.  This makes it possible
;   for sprites (or background tiles) to have a constant apparent
;   speed on screen even when the time needed to draw on screen
;   varies (if updaterate is used and properly set, lastframecnt will
;   always equal updaterate).
; - Try to keep sprites small or small in number.  The demo
;   included here draws & moves ten 5x6 sprites and takes around 13
;   frames to do so.  Reducing the sprite sizes to 3x3 reduces the
;   draw time to 5 frames, a very acceptable result.  For very large
;   animations, a mixture of small sprites for the outline and
;   background tiles for the interior may help to speed things up.
; - Finally, make use of the time estimates for screen update and
;   movespr to judge how much action can be supported well.
;
;
; Cross-assembled z80 using TASM.
;   tasm -80 -s -c -fff -g3 sprites.txt
;
; Converted to tap file using TAPUTILS by BusySoft
;   binto0 sprites.obj 3 32256
;   0totap -o sprites.tap sprites.000
;
;
; Memory Map
;
;   * begin contended memory *
;
;   4000..57ff  dfile 1 (even columns)
;   6000..77ff  dfile 2 (odd columns)
;
;   7e00..7f00	interrupt vector table
;   7f01..7fff  stack
;
;   * end contended memory *
;
;   8000..803f	row 0 background bitmap hi
;   8040, 48, 50, 58, 60, 68, 70, 78 dirty area
;   8079..80f6	9 sprite data structures
;   8100..813f	row 0 background bitmap lo
;   8140..81f5	13 sprite data structures
;   8200..823f  row 0 next char struct hi
;   8240..82f5	13 sprite data structures
;   8300..833f	row 0 next char struct lo
;   8340..83f5	13 sprite data structures
;   8400..843f	row 1 background bitmap hi
;   8440, 48, 50, 58, 60, 68, 70, 78 dirty area
;   8479..84f6	9 sprite data structures
;   8500..853f	row 1 background bitmap lo
;   8540..85f5	13 sprite data structures
;   8600..863f  row 1 next char struct hi
;   8640..86f5	13 sprite data structures
;   8700..873f	row 1 next char struct lo
;   8740..87f5	13 sprite data structures
;   8800..883f	row 2 background bitmap hi
;   8840, 48, 50, 58, 60, 68, 70, 78 dirty area
;   8879..88f6	9 sprite data structures
;   8900..893f	row 2 background bitmap lo
;   8940..89f5	13 sprite data structures
;   8a00..8a3f  row 2 next char struct hi
;   8a40..8af5	13 sprite data structures
;   8b00..8b3f	row 2 next char struct lo
;   8b40..8bf5	13 sprite data structures
;   8c00..8c3f	row 3 background bitmap hi
;   8c40, 48, 50, 58, 60, 68, 70, 78 dirty area
;   8c79..8cf6	9 sprite data structures
;   8d00..8d3f	row 3 background bitmap lo
;   8d40..8df5	13 sprite data structures
;   8e00..8e3f  row 3 next char struct hi
;   8e40..8ef5	13 sprite data structures
;   8f00..8f3f	row 3 next char struct lo
;   8f40..8ff5	13 sprite data structures
;   9000..903f	row 4 background bitmap hi
;   9040, 48, 50, 58, 60, 68, 70, 78 dirty area
;   9079..90f6	9 sprite data structures
;   9100..913f	row 4 background bitmap lo
;   9140..91f5	13 sprite data structures
;   9200..923f  row 4 next char struct hi
;   9240..92f5	13 sprite data structures
;   9300..933f	row 4 next char struct lo
;   9340..93f5	13 sprite data structures
;   9400..943f	row 5 background bitmap hi
;   9440, 48, 50, 58, 60, 68, 70, 78 dirty area
;   9479..94f6	9 sprite data structures
;   9500..953f	row 5 background bitmap lo
;   9540..95f5	13 sprite data structures
;   9600..963f  row 5 next char struct hi
;   9640..96f5	13 sprite data structures
;   9700..973f	row 5 next char struct lo
;   9740..97f5	13 sprite data structures
;   9800..983f	row 6 background bitmap hi
;   9840, 48, 50, 58, 60, 68, 70, 78 dirty area
;   9879..98f6	9 sprite data structures
;   9900..993f	row 6 background bitmap lo
;   9940..99f5	13 sprite data structures
;   9a00..9a3f  row 6 next char struct hi
;   9a40..9af5	13 sprite data structures
;   9b00..9b3f	row 6 next char struct lo
;   9b40..9bf5	13 sprite data structures
;   9c00..9c3f	row 7 background bitmap hi
;   9c40, 48, 50, 58, 60, 68, 70, 78 dirty area
;   9c79..9cf6	9 sprite data structures
;   9d00..9d3f	row 7 background bitmap lo
;   9d40..9df5	13 sprite data structures
;   9e00..9e3f  row 7 next char struct hi
;   9e40..9ef5	13 sprite data structures
;   9f00..9f3f	row 7 next char struct lo
;   9f40..9ff5	13 sprite data structures
;   a000..a03f	row 8 background bitmap hi
;   a040, 48, 50, 58, 60, 68, 70, 78 dirty area
;   a079..a0f6	9 sprite data structures
;   a100..a13f	row 8 background bitmap lo
;   a140..a1f5	13 sprite data structures
;   a200..a23f  row 8 next char struct hi
;   a240..a2f5	13 sprite data structures
;   a300..a33f	row 8 next char struct lo
;   a340..a3f5	13 sprite data structures
;   a400..a43f	row 9 background bitmap hi
;   a440, 48, 50, 58, 60, 68, 70, 78 dirty area
;   a479..a4f6	9 sprite data structures
;   a500..a53f	row 9 background bitmap lo
;   a540..a5f5	13 sprite data structures
;   a600..a63f  row 9 next char struct hi
;   a640..a6f5	13 sprite data structures
;   a700..a73f	row 9 next char struct lo
;   a740..a7f5	13 sprite data structures
;   a800..a83f	row 10 background bitmap hi
;   a840, 48, 50, 58, 60, 68, 70, 78 dirty area
;   a879..a8f6	9 sprite data structures
;   a900..a93f	row 10 background bitmap lo
;   a940..a9f5	13 sprite data structures
;   aa00..aa3f  row 10 next char struct hi
;   aa40..aaf5	13 sprite data structures
;   ab00..ab3f	row 10 next char struct lo
;   ab40..abf5	13 sprite data structures
;   ac00..ac3f	row 11 background bitmap hi
;   ac40, 48, 50, 58, 60, 68, 70, 78 dirty area
;   ac79..acf6	9 sprite data structures
;   ad00..ad3f	row 11 background bitmap lo
;   ad40..adf5	13 sprite data structures
;   ae00..ae3f  row 11 next char struct hi
;   ae40..aef5	13 sprite data structures
;   af00..af3f	row 11 next char struct lo
;   af40..aff5	13 sprite data structures
;   b000..b03f	row 12 background bitmap hi
;   b040, 48, 50, 58, 60, 68, 70, 78 dirty area
;   b100..b13f	row 12 background bitmap lo
;   b200..b23f  row 12 next char struct hi
;   b300..b33f	row 12 next char struct lo
;   b400..b43f	row 13 background bitmap hi
;   b440, 48, 50, 58, 60, 68, 70, 78 dirty area
;   b500..b53f	row 13 background bitmap lo
;   b600..b63f  row 13 next char struct hi
;   b700..b73f	row 13 next char struct lo
;   b800..b83f	row 14 background bitmap hi
;   b840, 48, 50, 58, 60, 68, 70, 78 dirty area
;   b900..b93f	row 14 background bitmap lo
;   ba00..ba3f  row 14 next char struct hi
;   bb00..bb3f	row 14 next char struct lo
;   bc00..bc3f	row 15 background bitmap hi
;   bc40, 48, 50, 58, 60, 68, 70, 78 dirty area
;   bd00..bd3f	row 15 background bitmap lo
;   be00..be3f  row 15 next char struct hi
;   bf00..bf3f	row 15 next char struct lo
;   c000..c03f	row 16 background bitmap hi
;   c040, 48, 50, 58, 60, 68, 70, 78 dirty area
;   c100..c13f	row 16 background bitmap lo
;   c200..c23f  row 16 next char struct hi
;   c300..c33f	row 16 next char struct lo
;   c400..c43f	row 17 background bitmap hi
;   c440, 48, 50, 58, 60, 68, 70, 78 dirty area
;   c500..c53f	row 17 background bitmap lo
;   c600..c63f  row 17 next char struct hi
;   c700..c73f	row 17 next char struct lo
;   c800..c83f	row 18 background bitmap hi
;   c840, 48, 50, 58, 60, 68, 70, 78 dirty area
;   c900..c93f	row 18 background bitmap lo
;   ca00..ca3f  row 18 next char struct hi
;   cb00..cb3f	row 18 next char struct lo
;   cc00..cc3f	row 19 background bitmap hi
;   cc40, 48, 50, 58, 60, 68, 70, 78 dirty area
;   cd00..cd3f	row 19 background bitmap lo
;   ce00..ce3f  row 19 next char struct hi
;   cf00..cf3f	row 19 next char struct lo
;   d000..d03f	row 20 background bitmap hi
;   d040, 48, 50, 58, 60, 68, 70, 78 dirty area
;   d100..d13f	row 20 background bitmap lo
;   d200..d23f  row 20 next char struct hi
;   d300..d33f	row 20 next char struct lo
;   d400..d43f	row 21 background bitmap hi
;   d440, 48, 50, 58, 60, 68, 70, 78 dirty area
;   d500..d53f	row 21 background bitmap lo
;   d600..d63f  row 21 next char struct hi
;   d700..d73f	row 21 next char struct lo
;   d800..d83f	row 22 background bitmap hi
;   d840, 48, 50, 58, 60, 68, 70, 78 dirty area
;   d900..d93f	row 22 background bitmap lo
;   da00..da3f  row 22 next char struct hi
;   db00..db3f	row 22 next char struct lo
;   dc00..dc3f	row 23 background bitmap hi
;   dc40, 48, 50, 58, 60, 68, 70, 78 dirty area
;   dd00..dd3f	row 23 background bitmap lo
;   de00..de3f  row 23 next char struct hi
;   df00..df3f	row 23 next char struct lo
;
;   df40..e5fa  sprite code
;
;   efc9..efff	various data structures
;   f020..ffff	horizontal rotate tables
;
;
; Important Address Layouts:
;
;   DFILE (64x24 chars, 512x192 pixels):
;
;     7   6   5   4   3   2   1   0
;   +---+---+---+---+---+---+---+---+
;   | 0 | 1 | D | B | B | S | S | S |    MSB
;   +---+---+---+---+---+---+---+---+
;
;     7   6   5   4   3   2   1   0
;   +---+---+---+---+---+---+---+---+
;   | L | L | L | C | C | C | C | C |    LSB
;   +---+---+---+---+---+---+---+---+
;
;   BB    = screen block
;   SSS   = character scan line
;   LLL   = character line within block
;   CCCCC = horizontal char position / 2
;   D     = horizontal char position % 2
;
;
;   Display List Structure Array
;
;     7   6   5   4   3   2   1   0
;   +---+---+---+---+---+---+---+---+
;   | 1 | R | R | R | R | R | W | W |    MSB
;   +---+---+---+---+---+---+---+---+
;
;     7   6   5   4   3   2   1   0
;   +---+---+---+---+---+---+---+---+
;   | 0 | 0 | C | C | C | C | C | C |    LSB
;   +---+---+---+---+---+---+---+---+
;
;   RRRRR  = character row (0..23)
;   CCCCCC = character column (0..63)
;   WW     = 00 for background bitmap HI
;          = 01 for background bitmap LO
;          = 10 for char struct ptr HI (0 = none)
;          = 11 for char struct ptr LO
;
;     7   6   5   4   3   2   1   0
;   +---+---+---+---+---+---+---+---+
;   | 0 | 1 | C | C | C | 0 | 0 | 0 |    LSB
;   +---+---+---+---+---+---+---+---+
;
;   CCC    = character column / 8
;
;     non-zero indicates chars within
;     row RRRRR, columns CCC000..CCC111
;     need update.
;
;
;   Horizontal Rotation Tables
;
;     7   6   5   4   3   2   1   0
;   +---+---+---+---+---+---+---+---+
;   | 1 | 1 | 1 | 1 | R | R | R | W |    MSB
;   +---+---+---+---+---+---+---+---+
;
;   RRR = bit rotation (1..7)
;   W   = 0 for remainder after byte rotated right
;       = 1 for part shifted out after right rotation on left side of byte
;
;   LSB = byte to rotate
;
;
; Important Data Structures
;
;   Display List Structure
;
;   +-------------------+
; 0 |               HI  | bit 7 = 1 means must update on screen, else don't
;   |-background bitmap-| points at 8-byte definition of background char (like UDG)
; 1 |               LO  |
;   +-------------------+
; 2 |               HI  | HI = 0 for none
;   |-first char struct-| points at char struct + 5
; 3 |               LO  |
;   +-------------------+
;
;
;   Char Structure
;
;   +-------------------+
; 0 |               HI  | HI = 0 for none
;   |-next in sprite   -| points at next char struct within sprite
; 1 |               LO  |
;   +-------------------+
; 2 |               HI  | HI = 0 for none, bit 7 = 0 for first char struct
;   |-prev char struct -| points at previous char struct + 10 in display list
; 3 |               LO  |
;   +-------------------+
; 4 | sprite plane      | lower number, closer to viewer
;   +-------------------+
; 5 |                   |
;   |-bitdef left      -| points at sprite def of sprite char to left of this one
; 6 |                   |
;   +-------------------+
; 7 |                   |
;   |-bitdef           -| points at sprite def for this char
; 8 |                   |
;   +-------------------+
; 9 | horizontal rot    | msb of horizontal rotation table to use
;   +-------------------|
;10 |               HI  | HI = 0 for none
;   |-next char struct -| points at next char struct + 5 in disp list
;11 |               LO  |
;   +-------------------+
;12 | reserved          | colour in 32 column mode
;   +-------------------+
;
;
;   Sprite Structure
;
;   +-------------------+
; 0 | row coordinate    | 0..23 (if on screen)
;   +-------------------+
; 1 | col coordinate    | 0..63 (if on screen)
;   +-------------------+
; 2 | col width         | in chars
;   +-------------------+
; 3 | row height        | in chars
;   +-------------------+
; 4 | hor rotation      | in pixels 0..7
;   +-------------------+
; 5 | vert rotation     | in pixels 0..7
;   +-------------------|
; 6 |               HI  | HI = 0 for none
;   |-first char struct-| points at char struct + 0
; 7 | in sprite     LO  |
;   +-------------------+
; 8 |                   |
;   |-last col in spr  -| points at char struct + 0
; 9 | char struct       |
;   +-------------------+
;10 |                   |
;   |-last char struct -| points at char struct + 0
;11 | in sprite         |
;   +-------------------+
;12 | sprite plane      | lower means on top
;   +-------------------+
;
;   Sprite Bitdef
;
;   Sprite bitdefs are defined in columns with each column's bitdef defined
;   contiguously.  Each column bitdef must have 7 leading 0,$ff pairs so that
;   the software can perform a vertical rotation of the sprite.  The column
;   bitdef pointer passed to CreateSpr and AddColSpr points at the bitdef
;   following these 7 leading 0,$ffs.  As with UDGs, the bitdef of each
;   column is defined in terms of a byte of 8 pixels.  Unlike UDGs, each 8
;   pixels is defined by two bytes: the first defines the graphic (like UDG)
;   and the next defines the mask or the portion of the screen that shows
;   through the sprite.  So each column bitdef of a sprite consists of 7
;   leading 0,$ff pairs followed by two bytes per vertical pixel in the column.
;   ALL COLUMN SPRITE BITDEFS CANNOT CROSS 256 BYTE BOUNDARIES!
;
;   This sprite package is character oriented, meaning all sprite sizes
;   and coordinates are defined in terms of character cells.  However
;   the software will do horizontal and vertical rotations to get pixel
;   precision on the display.  To place a sprite at pixel position (x,y) =
;   (412,134), a call to MoveSprAbs can be made with row coord = 134/8 = 16,
;   vertical rotation = 134%8 = 6, col coord = 412/8 = 51 and horizontal
;   rotation = 412%8 = 4.
;
;
;   Background Text Display
;
;   Besides sprites, this package also supports a background text display
;   at 64x24 resolution.  The PrintAt subroutine allows manipulation of
;   the text in the background.  Changes in the background are
;   automatically reflected on the display.  ALL 8-BYTE BACKGROUND UDG
;   DEFINITIONS CANNOT CROSS 256 BYTE BOUNDARIES AND MUST RESIDE IN
;   UPPER 32K!
;
;
;   Dynamic Block Memory Allocator
;
;   A memory allocator that allocates user-defined size memory blocks
;   is included.  See the source code for details.
;
;
; Entry Points
;
;   Initialize		init sprite package, start interrupt service routine, set video mode
;
;   CreateSpr		create sprite with one column, not in display list
;   AddColSpr		add column to existing sprite, column not in display list
;   DeleteSpr		release memory associated with sprite
;   RemoveDList		remove a sprite from the display list, making it invisible
;   Invalidate		mark rectangular area on screen as needing redraw
;   MoveSprAbs		move sprite to absolute position, automatically invalidate
;   MoveSprRel		move sprite to a new relative position, automatically invalidate
; 
;   PrintAt		place text (or background graphic) at row,col coord
;   ScreenStr		retrieve text (or background graphic) at row,col coord
;
;   InitBlk		add memory to block memory queues
;   BlockBestFit	satisfy memory request with smallest memory block available
;   BlockAlloc		satisfy memory request from one queue only
;   FreeBlock		return memory to queues for reuse
;
;   MultDEA		16 bit by 8 bit integer multiply
;   MultDEAC		16 bit by 16 bit integer multiply
;
; One 60 Hz frame = 58800 cycles

		.lsfirst

		.org $7e00
intvect		.block 257

		.org $efc9
GreyBlock	.byte $55,$aa,$55,$aa,$55,$aa,$55,$aa
		.byte 0,$ff,0,$ff,0,$ff,0,$ff
		.byte 0,$ff,0,$ff,0,$ff
NullSprPtr	.byte 0,$ff,0,$ff,0,$ff,0,$ff	; the blank sprite char
		.byte 0,$ff,0,$ff,0,$ff,0,$ff
		.org $efef
ISRBRANCH	jp ISR
updaterate	.equ 6			; target update rate in frames
isrflags	.byte 0			; bit 7 = updating screen (read)
					; bit 6 = rdy to update scr (write, sw spins on this)
framecount	.byte 0			; frame count since last update began
lastframecnt	.byte 0			; last frame count
MAXBLKS		.equ 4			; not enforced, num memory block queues
BLKTBL		.block 2*MAXBLKS	; must not straddle 256 byte boundary


rotatetbl	.equ $f000		; first 512 bytes not used
		.org $f200
		.block $0e00		; horizontal rotate table


;
; Interrupt Service Routine: Screen Updater, Music & Keys/Joysticks
;
; time : 281 + music/keys/etc

		.org $df40
ISR		push af
		push bc
		push de
		push hl
		ex af,af'
		push af
		push ix

		ld a,(framecount)
		inc a
		jp nz, nowrap
		cpl
nowrap		ld (framecount),a	; count number of frames since last update

		; do music, keys, etc.
		
		ld a,(isrflags)
		cp $80
		jr nc, ISRexit		; busy updating, don't start another update
		cp $40
		jr c, ISRexit		; not ready for another update
		ex af,af'
		ld a,(framecount)
ttemp		cp updaterate
		jr c, ISRexit		; not time for update yet
		ld (lastframecnt),a
		ex af,af'		; a = isrflags
		or $80
		ld (isrflags),a		; screen update in progress
		xor a
		ld (framecount),a

; screen update time : 160 + 9.110C + 273.25D + 262N + 29S + 1384A - 826Af
;
;   C = # chars on screen = 64*24
;   D = # areas needing update
;       (area is 8 horizontal chars grouped together)
;   N = # chars needing update
;   S = # chars needing update that contain at least one sprite
;   A = total sprite area in chars, only those chars needing update
;  Af = total fast sprite area in chars, only those chars needing update
;       (fast sprite = sprite with zero horizontal rotation)
;
;  Af <= A <= N, N/8 <= D <= min(C/8,N), S <= N

		ei
updatescrn	ld de,$4000		; de = top of screen
		ld hl,$8040		; hl = top of display list, dirty area
arealp		ld a,(hl)
		or a
		jp nz, dirtyarea	; if something needs update in this area
		ld a,4
		add a,e			; otherwise skip area (8 chars)
		ld e,a
		jp nz, notacrossblk	; check if a screen block was crossed
		ld a,8
		add a,d			; and adjust as necessary
		ld d,a
notacrossblk	ld a,8			; advance display list 8 chars
		add a,l
		ld l,a
		jp po, arealp		; if column still < 64, repeat arealp
reenter		ld l,$40		; column back to 0, dirty area
		ld a,4
		add a,h
		ld h,a			; advance the row
		cp $e0
		jp c, arealp		; do 24 rows

		di
		ld a,(isrflags)		; done display update so clear
		and $3f			; bits 7 and 6 of isrflags
		ld (isrflags),a
ISRexit		pop ix
		pop af
		ex af,af'
		pop hl
		pop de
		pop bc
		pop af
		ei
		reti

dirtyarea	xor a			; at least one char in this area of 8
		ld (hl),a		; needs update, mark this area as updated
		res 6,l			; hl = display list, background bitmap HI
		ld a,(hl)
		cp $80
		call nc, drawchar	; if char marked for update, draw it
		set 5,d			; move to next odd column
		inc l			; hl = next column display list, bgnd bitmap HI
		ld a,(hl)
		cp $80
		call nc, drawchar
		res 5,d			; next column is even, adjust de
		inc e
		inc l
		ld a,(hl)		; this sequence is repeated, once for each
		cp $80			; of the 8 chars in the dirty area
		call nc, drawchar
		set 5,d
		inc l
		ld a,(hl)
		cp $80
		call nc, drawchar
		res 5,d
		inc e
		inc l
		ld a,(hl)
		cp $80
		call nc, drawchar
		set 5,d
		inc l
		ld a,(hl)
		cp $80
		call nc, drawchar
		res 5,d
		inc e
		inc l
		ld a,(hl)
		cp $80
		call nc, drawchar
		set 5,d
		inc l
		ld a,(hl)
		cp $80
		call nc, drawchar
		res 5,d
		inc e			; after the last of 8 chars, check
		jp nz, notacrossblk2	; if a screen block is crossed
		ld a,8
		add a,d
		ld d,a
notacrossblk2	set 6,l			; hl = display list, dirty area
		inc l			; advance past 8th char in last dirty area
		jp po, arealp		; overflow not set if column still < 64
		jp reenter		; otherwise, reenter where row is increased

drawchar	ld b,a
		and $7f			; mark char as not needing update
		ld (hl),a
		inc h
		ld c,(hl)		; bc = ptr to background bitmap
		inc h			; hl = display struct, next char struct HI

		ld a,(bc)		; get background graphic
		ld (de),a		; write to screen
		inc d			; next scan line in char
		inc c			; next bitmap address
		ld a,(bc)		; repeat 7 more times for entire char
		ld (de),a
		inc d
		inc c
		ld a,(bc)
		ld (de),a
		inc d
		inc c
		ld a,(bc)
		ld (de),a
		inc d
		inc c
		ld a,(bc)
		ld (de),a
		inc d
		inc c
		ld a,(bc)
		ld (de),a
		inc d
		inc c
		ld a,(bc)
		ld (de),a
		inc d
		inc c
		ld a,(bc)
		ld (de),a
		ld a,d			; de points to top of char again
		and $f8
		ld d,a

		ld a,(hl)
		or a
		jp z, nosprites		; if no sprites in this char
		inc h
		push hl			; save display list, next LO
spriteloop	ld l,(hl)
		ld h,a			; hl = char struct + 5
		ld c,(hl)
		inc l
		ld b,(hl)
		inc l
		.db $dd \ ld l,c
		.db $dd \ ld h,b	; ix = ptr to bitdef left of this char
		ld c,(hl)
		inc l
		ld b,(hl)		; bc = ptr to char bitdef
		inc l
		push hl			; save char struct + 9
		ld h,(hl)		; h = msb of horizontal rotate table to use
		ld a,h
		and $0e
		jp z, norotatespr	; if there is no rotation, draw sprite the fast way

		ld a,(bc)		; a = graphic
		ld l,a
		ld a,(hl)		; a = graphic rotated right
		inc h
		ld l,(ix+0)		; l = graphic left side
		or (hl)			; a = graphic rotated right | graphic from left side
		ex af,af'		; a' = final graphic
		inc c			; advance to mask
		dec h
		ld a,(bc)		; a = mask
		ld l,a
		ld a,(hl)		; a = mask rotated right
		inc h
		ld l,(ix+1)		; l = mask left side
		or (hl)			; a = mask rotated right | mask from left side
		ex de,hl		; hl = screen address, d = msb hor rotate table
		and (hl)		; a = existing screen & mask
		ld e,a
		ex af,af'		; a = sprite graphic
		or e			; a = sprite graphic | remaining portion of screen
		ld (hl),a		; update screen
		ex de,hl		; de = screen address, h = msb hor rotate table
		inc d			; next scan line in char
		dec h
		inc c
		ld a,(bc)		; repeat 7 more times for entire char
		ld l,a
		ld a,(hl)
		inc h
		ld l,(ix+2)
		or (hl)
		ex af,af'
		inc c
		dec h
		ld a,(bc)
		ld l,a
		ld a,(hl)
		inc h
		ld l,(ix+3)
		or (hl)
		ex de,hl
		and (hl)
		ld e,a
		ex af,af'
		or e
		ld (hl),a
		ex de,hl
		inc d
		dec h
		inc c
		ld a,(bc)		; repeat
		ld l,a
		ld a,(hl)
		inc h
		ld l,(ix+4)
		or (hl)
		ex af,af'
		inc c
		dec h
		ld a,(bc)
		ld l,a
		ld a,(hl)
		inc h
		ld l,(ix+5)
		or (hl)
		ex de,hl
		and (hl)
		ld e,a
		ex af,af'
		or e
		ld (hl),a
		ex de,hl
		inc d
		dec h
		inc c
		ld a,(bc)		; repeat
		ld l,a
		ld a,(hl)
		inc h
		ld l,(ix+6)
		or (hl)
		ex af,af'
		inc c
		dec h
		ld a,(bc)
		ld l,a
		ld a,(hl)
		inc h
		ld l,(ix+7)
		or (hl)
		ex de,hl
		and (hl)
		ld e,a
		ex af,af'
		or e
		ld (hl),a
		ex de,hl
		inc d
		dec h
		inc c
		ld a,(bc)		; repeat
		ld l,a
		ld a,(hl)
		inc h
		ld l,(ix+8)
		or (hl)
		ex af,af'
		inc c
		dec h
		ld a,(bc)
		ld l,a
		ld a,(hl)
		inc h
		ld l,(ix+9)
		or (hl)
		ex de,hl
		and (hl)
		ld e,a
		ex af,af'
		or e
		ld (hl),a
		ex de,hl
		inc d
		dec h
		inc c
		ld a,(bc)		; repeat
		ld l,a
		ld a,(hl)
		inc h
		ld l,(ix+10)
		or (hl)
		ex af,af'
		inc c
		dec h
		ld a,(bc)
		ld l,a
		ld a,(hl)
		inc h
		ld l,(ix+11)
		or (hl)
		ex de,hl
		and (hl)
		ld e,a
		ex af,af'
		or e
		ld (hl),a
		ex de,hl
		inc d
		dec h
		inc c
		ld a,(bc)		; repeat
		ld l,a
		ld a,(hl)
		inc h
		ld l,(ix+12)
		or (hl)
		ex af,af'
		inc c
		dec h
		ld a,(bc)
		ld l,a
		ld a,(hl)
		inc h
		ld l,(ix+13)
		or (hl)
		ex de,hl
		and (hl)
		ld e,a
		ex af,af'
		or e
		ld (hl),a
		ex de,hl
		inc d
		dec h
		inc c
		ld a,(bc)		; repeat
		ld l,a
		ld a,(hl)
		inc h
		ld l,(ix+14)
		or (hl)
		ex af,af'
		inc c
		dec h
		ld a,(bc)
		ld l,a
		ld a,(hl)
		inc h
		ld l,(ix+15)
		or (hl)
		ex de,hl
		and (hl)
		ld e,a
		ex af,af'
		or e
		ld (hl),a
		ex de,hl

rejoin		ld a,d
		and $f8
		ld d,a			; de points to top of char on screen again
		pop hl			; hl = char struct + 9
		inc l
		ld a,(hl)		; take a look at next char struct ptr
		inc l
		or a
		jp nz, spriteloop	; if another sprite exists in this char, draw it too
		pop hl			; hl = display list, next char LO
		dec h
nosprites	dec h
		dec h			; hl = display list, bgnd bitmap HI
		ret

		; draw fast sprite char
norotatespr	ex de,hl		; hl = screen address
		ld a,(bc)
		ld e,a			; e = graphic
		inc c
		ld a,(bc)		; a = mask
		inc c
		and (hl)		; a = portion of screen remaining
		or e			; or in sprite graphic
		ld (hl),a		; write to screen
		inc h			; next scan line
		ld a,(bc)		; repeat 7 more times for entire char
		ld e,a
		inc c
		ld a,(bc)
		inc c
		and (hl)
		or e
		ld (hl),a
		inc h
		ld a,(bc)		; repeat
		ld e,a
		inc c
		ld a,(bc)
		inc c
		and (hl)
		or e
		ld (hl),a
		inc h
		ld a,(bc)		; repeat
		ld e,a
		inc c
		ld a,(bc)
		inc c
		and (hl)
		or e
		ld (hl),a
		inc h
		ld a,(bc)		; repeat
		ld e,a
		inc c
		ld a,(bc)
		inc c
		and (hl)
		or e
		ld (hl),a
		inc h
		ld a,(bc)		; repeat
		ld e,a
		inc c
		ld a,(bc)
		inc c
		and (hl)
		or e
		ld (hl),a
		inc h
		ld a,(bc)		; repeat
		ld e,a
		inc c
		ld a,(bc)
		inc c
		and (hl)
		or e
		ld (hl),a
		inc h
		ld a,(bc)		; repeat
		ld e,a
		inc c
		ld a,(bc)
		and (hl)
		or e
		ld (hl),a
		ex de,hl		; de = screen address
		jp rejoin


;
; Initialize Package
;

Initialize	di
		pop de			; move stack
		ld hl,$7fff-4		; 4 byte margin on stack
		ld sp,hl
		push de

		ld h,$80		; clear display list struct array
dlistlp1	ld l,0
dlistlp2	xor a
		ld (hl),GreyBlock/256	; background tile is grey block
		inc h
		ld (hl),GreyBlock%256
		inc h
		ld (hl),a
		inc h
		ld (hl),a
		inc l
		ld (hl),a
		dec h
		ld (hl),a
		dec h
		ld (hl),GreyBlock%256	; background tile is grey block
		dec h
		ld (hl),GreyBlock/256
		inc l
		ld a,l
		cp 64
		jp c, dlistlp2
		ld a,4
		add a,h
		ld h,a
		cp $e0
		jp c, dlistlp1

		; set up horizontal rotate table
		ld c,7			; rotate by C
rottbllp	ld a,c
		add a,a
		or rotatetbl/256
		ld h,a
		ld l,0
entrylp		ld b,c
		ld e,l
		xor a
rotlp		srl e
		rra
		djnz rotlp
		ld (hl),e
		inc h
		ld (hl),a
		dec h
		inc l
		jp nz, entrylp
		dec c
		jp nz, rottbllp

		; initialize memory block table
		ld hl,BLKTBL
		ld b,MAXBLKS*2
binit		ld (hl),0
		inc l
		djnz binit

		; allocate memory for sprite data structures
		ld hl,$8040
		ld de,13
malloclp	xor a
		push hl			; each pass through loop allocates
		ld b,9			;   48 sprite data structures
		ld l,$79
		call InitBlk		; these data structures must reside
		pop hl			;   in upper 32k
		inc h
		push hl
		ld b,13
		call InitBlk
		pop hl
		inc h
		push hl
		ld b,13
		call InitBlk
		pop hl
		inc h
		push hl
		ld b,13
		call InitBlk
		pop hl
		inc h
		ld a,h
		cp $b0
		jp c, malloclp		; steal memory in rows 0..11 inclusive
		
		ld a,$86
		out ($ff),a		; video mode 512x192, int enable, black on white

		; interrupt vector table
		ld a,intvect/256
		ld hl,intvect
		ld (hl),$ef
		ld de,intvect+1
		ld bc,256
		ldir

		ld i,a
		im 2
		ei
		ret


; Create Sprite
;
; enter:  B = #rows
;         C = sprite plane
;        DE = sprite column definition

; exit : IX = sprite structure address
;        carry for success
; time : 377 + 371*rows (success)

CreateSpr	push de
		xor a
		call BlockAlloc		; get memory for sprite struct
		jr nc, csfail1
		ld l,e
		ld h,d			; hl = sprite struct address
		; ld ixl,e		; ix = sprite struct address
		.db $dd \ ld l,e
		; ld ixh,d
		.db $dd \ ld h,d
		xor a
		ld (hl),a		; row position = 0
		inc l
		ld (hl),a		; col position = 0
		inc l
		ld (hl),1		; width = 1 char
		inc l
		ld (hl),b		; height in char rows
		inc l
		ld (hl),a		; no horizontal rotation
		inc l
		ld (hl),a		; no vertical rotation
		inc l
		ld (hl),a		; no next char struct
		ld (ix+12),c		; sprite plane
		push hl
		xor a
		call BlockAlloc		; get memory for char struct
		jr nc, csfail2
		ld (ix+8),e		; last column in sprite is
		ld (ix+9),d		; the one being made now
		jp csenter
csloop		push de
		push hl
		xor a
		call BlockAlloc		; get memory for char struct
		jr nc, csfail2
csenter		pop hl			; hl = next char struct ptr
		ld (hl),d		; store address of next char struct
		inc l
		ld (hl),e
		pop hl			; hl = sprite column def
		ex de,hl                ; de = spr col def, hl = char struct
		push hl
		xor a
		ld (hl),a		; no next char struct in sprite
		inc l
		inc l
		ld (hl),a		; no previous char struct in display list
		inc l
		inc l
		ld (hl),c		; sprite plane
		inc l
		ld (hl),NullSprPtr%256	; empty space to left of sprite
		inc l			; so sprite rotations will introduce
		ld (hl),NullSprPtr/256	; empty space at leftmost column
		inc l
		ld (hl),e		; sprite column bitdef
		inc l
		ld (hl),d
		inc l
		ld (hl),rotatetbl/256	; no horizontal rotation
		inc l
		ld (hl),a		; no next char struct in display list
		pop hl			; hl = char struct (next in sprite ptr)
		ld a,16
		add a,e
		ld e,a			; de = sprite column bitdef, next char
		djnz csloop		; for all rows in sprite
		ld (ix+10),l
		ld (ix+11),h		; last char struct in sprite
		scf			; success
		ret
csfail1		pop de
		ret
csfail2		pop hl
		pop de
		;ld e,ixl		; de = sprite struct address
		.db $dd \ ld e,l
		;ld d,ixh
		.db $dd \ ld d,h
		jp DeleteSpr


; Delete Sprite
;
; enter: DE = sprite structure address
; exit : no carry
; time : 190*(rows*cols+1)
;
; Warning: remove sprite from display list before deletion

DeleteSpr	ld a,6
		add a,e
		ld l,a
		ld h,d
		push hl			; hl = next in sprite ptr
		call FreeBlock		; free sprite structure
		pop hl
dsloop		ld a,(hl)
		or a
		ret z			; no more char structs in sprite
		inc l
		ld e,(hl)
		ld d,a			; de = next char struct
		push de
		call FreeBlock		; free char structure
		pop hl			; hl = next in sprite ptr (aka char struct)
		jp dsloop


; Add Column to Sprite
;
; enter: IX = sprite structure address
;        DE = sprite column definition
; exit : carry for success
; time : 321 + 475*rows

AddColSpr	ld a,e
		sub (ix+5)		; adjust sprite column address
		sub (ix+5)
		ld e,a			; for existing vertical rotation
		ld c,(ix+8)		; bc = first char struct in last
		ld b,(ix+9)		;      column of this sprite
		ld l,(ix+10)		; hl = last char struct in sprite
		ld h,(ix+11)
		exx
		ld b,(ix+3)		; b' = row height in chars
acloop		exx
		push de
		push hl
		xor a
		call BlockAlloc		; get memory for new char struct
		jr nc, acfail
		pop hl			; hl = last char struct in sprite (next ptr)
		ld (hl),d		; store address of new char struct
		inc l
		ld (hl),e
		pop hl			; hl = sprite column bitdef
		ex de,hl		; de = spr col bitdef, hl = char struct (next ptr)
		push hl
		push bc
		xor a
		ld (hl),a		; no next char struct in sprite
		inc l
		inc l
		ld (hl),a		; no previous char struct in display list
		inc l
		inc l
		ld a,(ix+12)
		ld (hl),a		; sprite plane
		inc l			; hl = char struct bitdef left
		ld a,7
		add a,c
		ld c,a			; bc = last column char struct bitdef
		ld a,(bc)		; store bitdef left in char struct
		ld (hl),a
		inc c
		inc l
		ld a,(bc)
		ld (hl),a
		inc l
		inc c
		ld (hl),e		; store bitdef in char struct
		inc l
		ld (hl),d
		inc l
		ld a,16
		add a,e
		ld e,a			; next char in sprite column definition
		ld a,(bc)		; store current horizontal rotation in char struct
		ld (hl),a
		inc l
		ld (hl),0		; no next char struct in display list
		pop hl			; hl = last column char struct
		ld b,(hl)
		inc l
		ld c,(hl)		; bc = next char struct in last column
		pop hl			; hl = last char struct in sprite
		exx
		djnz acloop		; add char for all rows
		exx
		ld e,(ix+10)		; de = old last char struct in sprite
		ld d,(ix+11)
		ld (ix+10),l		; store new last char struct
		ld (ix+11),h
		ex de,hl
		ld d,(hl)
		inc l
		ld e,(hl)		; de = char struct following old last char struct
		ld (ix+8),e		; becomes last column in sprite
		ld (ix+9),d
		inc (ix+2)		; sprite width increases by 1
		scf			; success
		ret
acfail		pop hl
		pop de
		ld l,(ix+10)
		ld h,(ix+11)
		jp dsloop


; Invalidate Rectangle
;
; enter:  D = row position
;         E = col position
;         B = width in chars
;         C = height in chars
; time : 10 + 82*(rows+1)*cols

Invalidate	ld a,d			; is current row position on screen?
		cp 24
		jr nc, skiprow		; no - skip row
		rla
		rla
		or $80
		and $fc
		ld h,a			; h = msb of display list structure
		push bc
		ld c,e			; c = col position
invcol		ld a,c
		cp 64			; is current col position on screen?
		jr nc, skipcol		; no - skip col
		ld l,a
		set 7,(hl)		; mark char as needing update
		and $38
		or $40
		ld l,a
		ld (hl),$ff		; mark area as needing update
skipcol		inc c			; next col
		djnz invcol		; for all columns
		pop bc
skiprow		inc d			; next row pos
		dec c
		jp nz, Invalidate	; for all rows
		ret


; Remove Sprite from Display List
;
; enter: IX = sprite structure address
; time : 57 + 281*rows*cols

RemoveDList	ld h,(ix+6)
		ld l,(ix+7)		; hl = first char struct in sprite
		ld a,h
rdloop		or a
		ret z			; no more char structs in sprite
		push hl			; save next char struct ptr
		inc l
		inc l			; hl = current + 2
		ld a,(hl)		; check if char in display list
		or a
		jp z, rdcont		; not in display list, skip it
		ld (hl),0		; mark as not in display list
		inc l
		ld e,(hl)
		ld d,a			; de = previous char struct + 10
		ld a,7
		add a,l
		ld l,a			; hl = char struct + 10
		ld a,(hl)
		ld (hl),0		; mark no next char struct in display list
		inc l
		ld l,(hl)
		ld h,a			; hl = next char struct + 5
		ex de,hl		; de = next char struct + 5, hl = previous ch str + 10
		ld a,h
		cp $80			; this char struct was first in display list?
		jr nc, notfirstone
		or $80
		ld h,a			; hl actually is display list next ptr HI
		ld (hl),d		; change next ptr
		inc h
		ld (hl),e
		dec h			; hl = display list next ptr HI
		res 7,h			; mark it as first char struct
		jp contrmv
notfirstone	ld (hl),d		; change next ch str ptr
		inc l
		ld (hl),e
		dec l
contrmv		ex de,hl		; hl = next ch str + 5, de = prev ch str + 10
		ld a,h
		or a
		jr z, rdcont		; nothing in display list after this char str
		dec l
		dec l			; hl = next ch str + 3
		ld (hl),e		; change prev ch str ptr
		dec l
		ld (hl),d
rdcont		pop hl			; next char struct ptr
		ld a,(hl)
		inc l
		ld l,(hl)
		ld h,a			; hl = char struct
		jp rdloop


; Move Sprite Absolute
;
; enter: IX = sprite structure address
;        BC = animate bitdef displacement (0 for no animation)
;         H = new row coord
;         L = new col coord
;         D = new horizontal rotation (0..7)
;         E = new vertical rotation (0..7)
;
; time : 384 + 134C + 353RC --> row, col coord do not change
;        412 + 134C + 1015RC + 96PRC
;
;         R = # rows
;         C = # cols
;         P = avg # sprites on lower plane in each char + 1
;             (P >= 1)

MoveSprAbs	exx
		ld d,(ix+0)
		ld e,(ix+1)
		ld b,(ix+2)
		ld c,(ix+3)
		call Invalidate
		exx
		ld a,h			; avoid changing coord if we can
		cp (ix+0)		; to avoid expensive insertions and
		jp nz, changecoord	; deletions from the display list
		ld a,l
		cp (ix+1)
		jp nz, changecoord
		ld iy,adjRotate
		jp mvsprabs
changecoord	ld iy,adjDListRot
		ld (ix+0),h		; store new row coord
		ld (ix+1),l		; store new col coord
mvsprabs	ld a,d
		and $07
		ld (ix+4),a		; store new horizontal rotation
		rla
		or rotatetbl/256
		ld d,a			; d = msb hor rotation table address
		ld a,e
		and $07
		ld e,a
		sub (ix+5)
		add a,a
		neg
		ld (ix+5),e		; store new vertical rotation
		ld l,a
		ld h,0			; sign extend a into hl
		jp p, notnegative
		dec h
notnegative	add hl,bc
		ld c,l
		ld b,h
		exx			; bc' = bitdef displacement, d' = msb hor rot tbl
		ld a,6
		.db $dd \ add a,l	; a = ixl + 6
		ld l,a
		.db $dd \ ld a,h	; a = ixh
		ld h,a			; hl = ptr to ptr to first char struct in sprite
		ld c,(ix+1)		; c = col coord
mvcolloop	ld b,(ix+0)		; b = row coord
		ld a,(ix+3)
mvrowloop	ex af,af'		; a' = row height in chars
		ld a,(hl)
		or a
		ret z			; no more char structs in sprite
		inc l
		ld l,(hl)
		ld h,a			; hl = current char struct + 0
		jp (iy)

adjRotate	ld a,9
		add a,l
		ld l,a			; hl = curr + 9
		jp adjrot

notdisplayed	pop hl			; hl = curr + 2
		ld a,7
		add a,l
		ld l,a			; hl = curr + 9
		jp adjrot

; stack = curr + 2, hl = DList next LO, de = next + 4
insrtheadb	ex (sp),hl		; hl = curr + 2, stack = DList next LO
		inc e			; de = next + 5
		ld a,9
		add a,l
		ld l,a			; hl = curr + 11
		ld (hl),e
		dec l			; hl = curr + 10
		ld (hl),d		; curr's next ptr = next + 5
		dec e
		dec e			; de = next + 3
		ex de,hl		; hl = next + 3, de = curr + 10
		ld (hl),e
		dec l			; hl = next + 2
		ld (hl),d		; next's prev ptr = curr + 10
		pop hl			; hl = DList next LO
		jp finishhead

; stack = curr + 2, hl = DList next ptr HI
insrtheada	pop de			; de = curr + 2
		ld a,8
		add a,e
		ld e,a			; de = curr + 10
		xor a
		ld (de),a		; curr's next ptr = nothing
		inc h			; hl = DList next LO
finishhead	ld a,e
		sub 5
		ld e,a			; de = curr + 5
		ld (hl),e
		dec h			; hl = DList next HI
		ld (hl),d		; DList next ptr = curr + 5
		dec e
		dec e			; de = curr + 3
		ex de,hl		; hl = curr + 3, hl = DList next HI
		ld (hl),e
		dec l
		ld a,d
		and $7f
		ld (hl),a		; curr's prev ptr = DList next HI (msb = 0)
		ld a,7
		add a,l
		ld l,a			; hl = curr + 9
		jp adjrot

adjDListRot	inc l			; first remove char struct from DList
		inc l
		push hl			; save char struct + 2 (current + 2)
		ld a,(hl)		; can't remove char struct from DList
		or a			;   if not there now
		jr z, notindlist
		ld (hl),0		; mark as not in DList
		inc l			; hl = curr + 3
		ld e,(hl)
		ld d,a			; de = prev + 10
		ld a,7
		add a,l
		ld l,a			; hl = curr + 10
		ld a,(hl)
		ld (hl),0		; mark no next char struct in DList
		inc l
		ld l,(hl)
		ld h,a			; hl = next + 5
		ex de,hl		; de = next + 5, hl = prev + 10
		ld a,h
		cp $80			; special case: if bit 7 of prev reset,
		jr nc, notfirstone2	;   prev is actually in display list array
		or $80			; in DList array, set bit 7 to correct address
		ld h,a			; hl = DList next char HI
		ld (hl),d
		inc h
		ld (hl),e		; DList next char points at next + 5
		dec h			; hl = DList next char HI
		res 7,h			; mark as in DList array
		jp wasfirstone
notfirstone2	ld (hl),d
		inc l
		ld (hl),e		; prev's next ptr points at next + 5
		dec l			; hl = prev + 10
wasfirstone	ex de,hl		; de = prev + 10, hl = next + 5
		ld a,h
		or a
		jr z, nonext1		; if msb = 0, there is no next char struct
		dec l
		dec l			; hl = next + 3
		ld (hl),e
		dec l
		ld (hl),d		; replace next's previous ptr
notindlist	.equ $
nonext1		ld a,c			; now put curr char struct in new location in DList
		cp 64
		jr nc, notdisplayed
		ld a,b
		cp 24
		jr nc, notdisplayed	; make sure curr is on screen
		rla
		rla
		or $80
		and $fc
		ld h,a			; msb of DList struct
		ld a,c
		and $38
		or $40
		ld l,a
		ld (hl),$ff		; mark area as needing update
		ld l,c			; hl = DList struct bgnd bitmap HI
		set 7,(hl)		; mark char as needing update
		inc h
		inc h			; hl = DList next HI
		ld a,(hl)
		or a
		jp z, insrtheada	; no other char structs here
		inc h
		ld e,(hl)
		ld d,a
		dec e			; de = next char struct + 4
		ld a,(de)
		cp (ix+12)
		jp c, insrtheadb	; next char struct on higher sprite plane
		ex de,hl
pdloop		ex de,hl		; next becomes new tentative prev
		ld a,6			; de = tentative prev + 4
		add a,e
		ld l,a
		ld h,d			; hl = prev + 10
		ld a,(hl)
		or a
		jr z, puthere		; no more char structs, at end
		inc l
		ld l,(hl)
		ld h,a
		dec l			; hl = next char struct + 4
		ld a,(hl)
		cp (ix+12)
		jp nc, pdloop		; next char struct on lower sprite plane
puthere		pop hl			; hl = current + 2
		ld a,6			; now we insert current after prev
		add a,e
		ld e,a			; de = prev + 10
		ld (hl),d
		inc l
		ld (hl),e		; curr's prev ptr = prev + 10
		ld a,7
		add a,l
		ld l,a			; hl = curr + 10
		ld a,(de)
		ld (hl),a
		inc l
		inc e
		ld a,(de)
		ld (hl),a		; curr's next ptr = prev's next ptr
		ld a,l
		sub 6
		ld l,a
		ex de,hl		; de = curr + 5, hl = prev + 11
		ld (hl),e
		dec l			; hl = prev + 10
		ld (hl),d		; prev's next ptr = curr + 5
		ld a,5
		add a,e
		ld e,a			; de = curr + 10
		ld a,(de)
		or a
		jp z, nonext2		; if no next char struct
		ld h,a
		inc e			; de = curr + 11
		ld a,(de)
		sub 2
		ld l,a			; hl = next + 3
		dec e			; de = curr + 10
		ld (hl),e
		dec l
		ld (hl),d		; next's prev ptr = curr + 10
nonext2		dec e
		ex de,hl		; hl = curr + 9

adjrot		push hl
		exx
		pop hl			; hl = curr + 9, bc = bitdef disp, d = msb hor rot tbl
		ld (hl),d		; store msb horizontal rotate tbl
		dec l			; hl = curr + 8
		ld a,d			; save d
		ld d,(hl)
		dec l			; hl = curr + 7
		ld e,(hl)
		ex de,hl		; de = curr + 7, hl = bitdef
		add hl,bc		; add animate + vertical rotate displacement
		ex de,hl		; hl = curr + 7, de = new bitdef
		ld (hl),e
		inc l
		ld (hl),d		; store new bitdef
		dec l
		dec l			; hl = curr + 6
		ld d,(hl)
		dec l			; hl = curr + 5
		ld e,(hl)		; de = bitdef left
		ex de,hl
		add hl,bc		; add animate + vert rot disp
		ex de,hl		; de = new bitdef left, hl = curr + 5
		ld (hl),e
		inc l
		ld (hl),d		; store new bitdef left
		ld d,a			; restore d
		exx			; hl = curr + 9

		ld a,l
		sub 9
		ld l,a			; hl = curr + 0
		inc b			; next row coord
		ex af,af'		; a = row height count
		dec a
		jp nz, mvrowloop	; more rows in this column
		inc c			; row done, move to next column
		jp mvcolloop


; Move Sprite Relative
;
; enter: IX = sprite structure address
;        BC = animate bitdef displacement (0 for no animation)
;         H = relative row coord
;         L = relative col coord
;         D = relative horizontal rotation (0..7)
;         E = relative vertical rotation (0..7)
; time : 244 + MoveSprAbs

MoveSprRel	ld a,(ix+4)		; current horizontal rotation
		add a,d
		ld d,a
		sra a
		sra a
		sra a
		add a,l
		add a,(ix+1)
		ld l,a			; l = absolute column position
		ld a,d
		cp $80
		jp c, mvpos1
		add a,8
mvpos1		and $07
		ld d,a			; d = absolute horizontal rotation
		ld a,(ix+5)		; current vertical rotation
		add a,e
		ld e,a
		sra a
		sra a
		sra a
		add a,h
		add a,(ix+0)
		ld h,a			; h = absolute row position
		ld a,e
		cp $80
		jp c, mvpos2
		add a,8
mvpos2		and $07
		ld e,a			; e = absolute vertical rotation
		jp MoveSprAbs


; Print Background Bitmap at Character Position
;
; enter:  A = row position (0..23)
;         L = col position (0..63)
;        DE = UDG address >= $8000
; exit : HL = address of (A,L) in display list struct bgnd bitmap HI, dirty area
; used : af, hl
; time : 90

PrintAt		rla
		rla
		and $fc
		or $80
		ld h,a
		ld (hl),d
		inc h
		ld (hl),e
		dec h
		ld a,l
		and $38
		or $40
		ld l,a
		ld (hl),$ff
		ret


; SCREEN$(row,col)
;
; enter:  A = row position (0..23)
;         L = col position (0..63)
; exit : HL = address of (A,L) in display list struct background bitmap LO
;        DE = UDG address possibly with bit 15 reset
; used : af, de, hl
; time : 54

ScreenStr	rla
		rla
		and $fc
		or $80
		ld h,a
		ld d,(hl)
		inc h
		ld e,(hl)
		ret

; Multiply 16 by 8
;
; enter: DE = 16 bit multiplicand
;         A = 8 bit multiplicand
; exit : HL = A*DE % 65536
; used : F, B, HL
; time : 414

MultDEA		ld hl,0
		ld b,8
m8lp		add hl,hl
		rlca 
		jp nc, noadd8
		add hl,de
noadd8		djnz m8lp
		ret


; Multiply 16 by 16
;
; enter: DE = 16 bit multiplicand
;        AC = 16 bit multiplicand
; exit : HL = AC*DE % 65536
; used : AF, BC, HL
; time : 934

MultDEAC	ld hl,0
		ld b,16
m16lp		add hl,hl
		sla c
		rla 
		jp nc, noadd16
		add hl,de
noadd16		djnz m16lp
		ret


; Block Memory Allocator

; Allocates fixed size blocks of memory from one of a user-defined number
; of pools.

; Entry Points: BlockBestFit, BlockAlloc, FreeBlock, InitBlockPool

; BLKTBL contains an array of pointers to the first free block in
; each pool, with each index into the table identifying a particular
; pool.  Each pool contains a linked list of free memory blocks of
; fixed size, determined by the user.  All pools are initialized
; with InitBlockPool.


; BlockBestFit
;
; Enter:  A     = Pool #, 0..MAXBLKS-1
;         B     = # Pools to check, 1..MAXBLKS-A
; Exit :  DE    = Address of memory block
;         Carry = Success
; Time :  Success   97P+45, P = #pools examined = Bout-Bin+1
;         Fail      97Bin+5
; Uses :  AF, B, HL, DE
;
; If the pools are arranged such that pool i contains memory blocks
; that are smaller than pool j, with i<j, then this routine attempts
; to satisfy the memory request by allocating from pool A, then pool A+1,
; etc. up to pool A+B-1, ensuring that the memory block returned will
; be at least as large as the block requested.

BlockBestFit	CALL BlockAlloc
		RET C
		DJNZ bbf
		RET
bbf		CALL BlockAgain
		RET C
		DJNZ bbf
		RET


; BlockAlloc
;
; Enter:  A     = Pool #, 0..MAXBLKS-1
; Exit :  DE    = Address of memory block
;         Carry = Success
; Time :  Success   114
;         Fail      62
; Uses :  AF, HL, DE

BlockAlloc	ADD A,A
		ADD A,BLKTBL % 256
		LD L,A
		LD H,BLKTBL / 256
alloc		LD E,(HL)
		INC L
		LD D,(HL)
		LD A,D
		OR E
		RET Z
		INC DE
		LD A,(DE)
		LD (HL),A
		DEC L
		DEC DE
		LD A,(DE)
		LD (HL),A
		SCF
		RET

BlockAgain	INC L
		INC L
		JP alloc


; FreeBlock
;
; Enter:  DE    = address of a block, as returned by BlockAlloc
; Exit :  none
; Time :  111
; Uses :  AF, BC, HL, DE
;
; Returns the memory block to the correct pool for use later.

FreeBlock	DEC DE
		LD A,(DE)
fb1		INC DE
		ADD A,A
		ADD A,BLKTBL % 256
		LD L,A
		LD H,BLKTBL / 256
fb2		LD C,(HL)
		INC L
		LD B,(HL)
		LD (HL),D
		DEC L
		LD (HL),E
		EX DE,HL
		LD (HL),C
		INC HL
		LD (HL),B
		RET


; InitBlk
;
; Enter:  A     = Pool #, 0..MAXBLKS-1
;         B     = # of memory blocks to add to pool A
;         DE    = size of memory blocks in bytes (not including hidden ID byte)
;         HL    = address of free memory from which to draw these blocks
; Exit :  HL    = address just past the memory reserved
; Uses :  F, B, HL, BC', HL', DE' 

InitBlk		PUSH AF
		PUSH HL
		EXX
		POP DE
		CALL FrmtBlk
ib1		EXX
		INC HL
		ADD HL,DE
		POP AF
		DJNZ ib2
		RET
ib2		PUSH AF
		PUSH HL
		EXX
		POP HL
		CALL FrmtAgain
		JP ib1

FrmtBlk		LD (DE),A
		JP fb1

FrmtAgain	EX DE,HL
		LD (DE),A
		INC DE
		JP fb2

THEEND		.equ $


;
; Sprites Test
;
; Ten 5x6 sprites moving at various speeds
; and twenty 2x2 animated background stars.
;
; One dynamic message showing frame count.
;

numspr		.equ 10
; each sprite: sprite struct address, dx (pixels), dy (pixels)
sprtable	.db 0,0,1,1,0,0,2,1,0,0,1,2,0,0,3,5,0,0,5,3
		.db 0,0,4,6,0,0,7,3,0,0,5,1,0,0,6,3,0,0,7,4

numstars	.equ 20
; each star: row coord, col coord, animation frame (0..7)
startable	.db 09,35,0,09,39,0,09,43,0,09,47,0,09,51,0
		.db 12,35,2,12,39,2,12,43,2,12,47,2,12,51,2
		.db 15,35,4,15,39,4,15,43,4,15,47,4,15,51,4
		.db 18,35,6,18,39,6,18,43,6,18,47,6,18,51,6

message1	.byte "Frame Count:",0
message2	.byte "Target Update Rate:",0
message3	.byte "0",0
CHARS		.equ 15360		; char set in rom -- fix for TC2048
NEWCHARS	.equ CharSet-256

		.org $e800
TEST		call Initialize

		; can't use char set in rom because it's
		; located at address < $8000 so copy most
		; of it into top 32k
		ld hl,CHARS+256
		ld de,NEWCHARS+256
		ld bc,712
		ldir

		; print static part of messages
		ld hl,message1
		ld b,1
		ld c,1
		call printmsg
		ld hl,message2
		ld b,3
		ld c,1
		call printmsg
		ld a,(ttemp+1)
		add a,'0'
		ld (message3),a
		ld hl,message3
		ld b,3
		ld c,21
		call printmsg

		; initialize 10 sprites
donemsg		ld a,numspr
		ld hl,sprtable
		ld bc,$0501
initloop	ex af,af'
		push hl
		push bc
		ld de,col1
		call CreateSpr
		ld de,col2
		call AddColSpr
		ld de,col3
		call AddColSpr
		ld de,col4
		call AddColSpr
		ld de,col5
		call AddColSpr
		ld de,col6
		call AddColSpr
		pop bc
		pop hl
		; ld a,ixl
		.db $dd \ ld a,l
		ld (hl),a
		inc hl
		; ld a,ixh
		.db $dd \ ld a,h
		ld (hl),a
		inc hl
		inc hl
		inc hl
		ex af,af'
		inc c
		dec a
		jp nz, initloop

loop		ld a,(isrflags)
		or $40
		ld (isrflags),a		; okay to redraw screen
spin		ld a,(isrflags)
		and $40
		jr nz, spin		; wait for redraw to finish

		; move sprites
		ld b,numspr
		ld hl,sprtable
chloop		ld e,(hl)
		inc hl
		ld d,(hl)
		; ld ixl,e
		.db $dd \ ld l,e
		; ld ixh,d
		.db $dd \ ld h,d
		inc hl
		ld d,(hl)
		inc hl
		ld e,(hl)
		inc hl
		push bc
		push hl
		ld hl,0
		ld bc,0
		call MoveSprRel
		pop hl
		pop bc
		ld a,(ix+0)
		cp 24
		jr c, keepdy
		dec hl
		ld a,(hl)
		neg
		ld (hl),a
		inc hl
keepdy		ld a,(ix+1)
		cp 64
		jr c,keepdx
		dec hl
		dec hl
		ld a,(hl)
		neg
		ld (hl),a
		inc hl
		inc hl
keepdx		djnz chloop

		; animate background stars
		ld b,numstars
		ld hl,startable
starloop	push bc
		ld b,(hl)
		inc hl
		ld c,(hl)
		inc hl
		ld a,(hl)
		inc a
		and $07
		ld (hl),a
		inc hl
		push hl
		ld de,StarA
		ld l,a
		ld h,0
		add hl,hl
		add hl,hl
		add hl,hl
		add hl,hl
		add hl,hl
		add hl,de
		ex de,hl
		ld l,c
		ld a,b
		call PrintAt
		ld hl,8
		add hl,de
		ex de,hl
		inc c
		ld l,c
		ld a,b
		call PrintAt
		ld hl,8
		add hl,de
		ex de,hl
		dec c
		inc b
		ld l,c
		ld a,b
		call PrintAt
		ld hl,8
		add hl,de
		ex de,hl
		inc c
		ld l,c
		ld a,b
		call PrintAt
		pop hl
		pop bc
		djnz starloop

		; print update time in frames
		ld a,(lastframecnt)
		push af
		ld de,NEWCHARS
		rra
		rra
		rra
		rra
		and $0f
		cp $0a
		jr c,isdigit1
		add a,'A'-'0'-10
isdigit1	add a,'0'
		ld l,a
		ld h,0
		add hl,hl
		add hl,hl
		add hl,hl
		add hl,de
		ex de,hl
		ld a,1
		ld l,14
		call PrintAt
		pop af
		ld de,NEWCHARS
		and $0f
		cp $0a
		jr c,isdigit2
		add a,'A'-'0'-10
isdigit2	add a,'0'
		ld l,a
		ld h,0
		add hl,hl
		add hl,hl
		add hl,hl
		add hl,de
		ex de,hl
		ld a,1
		ld l,15
		call PrintAt
		jp loop

printmsg	ld a,(hl)
		or a
		ret z
		inc hl
		push hl
		ld de,NEWCHARS
		ld l,a
		ld h,0
		add hl,hl
		add hl,hl
		add hl,hl
		add hl,de
		ex de,hl
		ld l,c
		ld a,b
		call PrintAt
		pop hl
		inc c
		jr printmsg


		; picture of a man sprite
		.org $ea00
		.db 0,$ff,0,$ff,0,$ff,0,$ff,0,$ff,0,$ff,0,$ff
col1		.db $ff,$00,$c0,$00,$a0,$00,$90,$00,$8f,$00,$88,$07,$88,$07,$88,$07
		.db $88,$07,$88,$07,$88,$07,$88,$07,$88,$07,$88,$07,$88,$07,$88,$07
		.db $88,$07,$88,$07,$88,$07,$88,$07,$88,$07,$88,$07,$88,$07,$88,$07
		.db $88,$07,$88,$07,$88,$07,$8f,$00,$90,$00,$a0,$00,$c0,$00,$ff,$00
		.db 0,$ff,0,$ff,0,$ff,0,$ff,0,$ff,0,$ff,0,$ff,0,$ff

col2		.db $ff,$00,$00,$00,$00,$00,$00,$00,$ff,$00,$00,$ff,$00,$ff,$00,$ff
		.db $00,$ff,$00,$ff,$00,$ff,$00,$00,$44,$00,$6c,$00,$54,$00,$54,$00
		.db $44,$00,$44,$00,$44,$00,$44,$00,$00,$00,$00,$ff,$00,$ff,$00,$ff
		.db $00,$ff,$00,$ff,$00,$ff,$ff,$00,$00,$00,$00,$00,$00,$00,$ff,$00
		.db 0,$ff,0,$ff,0,$ff,0,$ff,0,$ff,0,$ff,0,$ff,0,$ff

col3		.db $ff,$00,$00,$00,$00,$00,$00,$00,$ff,$00,$00,$ff,$00,$ff,$00,$ff
		.db $00,$ff,$00,$ff,$00,$ff,$00,$00,$21,$00,$51,$00,$89,$00,$89,$00
		.db $f9,$00,$89,$00,$89,$00,$89,$00,$00,$00,$00,$ff,$00,$ff,$00,$ff
		.db $00,$ff,$00,$ff,$00,$ff,$ff,$00,$00,$00,$00,$00,$00,$00,$ff,$00
		.db 0,$ff,0,$ff,0,$ff,0,$ff,0,$ff,0,$ff,0,$ff,0,$ff

		.org $eb00
		.db 0,$ff,0,$ff,0,$ff,0,$ff,0,$ff,0,$ff,0,$ff
col4		.db $ff,$00,$00,$00,$00,$00,$00,$00,$ff,$00,$00,$ff,$00,$ff,$00,$ff
		.db $00,$ff,$00,$ff,$00,$ff,$00,$07,$10,$07,$90,$07,$50,$07,$50,$07
		.db $50,$07,$30,$07,$30,$07,$10,$07,$00,$07,$00,$ff,$00,$ff,$00,$ff
		.db $00,$ff,$00,$ff,$00,$ff,$ff,$00,$00,$00,$00,$00,$00,$00,$ff,$00
		.db 0,$ff,0,$ff,0,$ff,0,$ff,0,$ff,0,$ff,0,$ff,0,$ff

col5		.db $ff,$00,$03,$00,$05,$00,$09,$00,$f1,$00,$11,$e0,$11,$e0,$11,$e0
		.db $11,$e0,$11,$e0,$11,$e0,$11,$e0,$11,$e0,$11,$e0,$11,$e0,$11,$e0
		.db $11,$e0,$11,$e0,$11,$e0,$11,$e0,$11,$e0,$11,$e0,$11,$e0,$11,$e0
		.db $11,$e0,$11,$e0,$11,$e0,$f1,$00,$09,$00,$05,$00,$03,$00,$ff,$00
		.db 0,$ff,0,$ff,0,$ff,0,$ff,0,$ff,0,$ff,0,$ff,0,$ff

col6		.db $00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff
		.db $00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff
		.db $00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff
		.db $00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff
		.db 0,$ff,0,$ff,0,$ff,0,$ff,0,$ff,0,$ff,0,$ff,0,$ff

		; animated background star, 8 frames
		.org $ec00
StarA		.db $55,$aa,$54,$a9,$01,$7f,$3e,$8f
		.db $95,$aa,$95,$ca,$c0,$fe,$bc,$f8
		.db $45,$a6,$57,$a7,$4e,$ac,$49,$92
		.db $a1,$6a,$e5,$f2,$75,$62,$19,$0a

StarB		.db $41,$a8,$4c,$a6,$57,$a7,$57,$85
		.db $55,$aa,$51,$04,$19,$fa,$71,$e2
		.db $1d,$7e,$ff,$07,$57,$a3,$52,$aa
		.db $d1,$78,$fc,$86,$10,$2a,$55,$aa

StarC		.db $55,$aa,$14,$80,$71,$3f,$1e,$8d
		.db $25,$6a,$e5,$ea,$e5,$ea,$e1,$b8
		.db $1d,$9e,$3f,$79,$c0,$0a,$55,$aa
		.db $ff,$b8,$e1,$ea,$e5,$6a,$65,$2a

StarD		.db $52,$a2,$56,$a7,$07,$ff,$fe,$3d
		.db $55,$aa,$55,$2a,$00,$fe,$7c,$f8
		.db $0d,$a7,$56,$a7,$4f,$ae,$4c,$a0
		.db $b1,$e2,$e5,$f2,$39,$0a,$41,$aa

StarE		.db $51,$a8,$4c,$ae,$47,$a7,$46,$8d
		.db $49,$9a,$31,$72,$e5,$ea,$65,$b2
		.db $1f,$7d,$ff,$03,$53,$a9,$55,$a9
		.db $f8,$7e,$ff,$80,$95,$2a,$55,$2a

StarF		.db $55,$aa,$54,$08,$61,$3f,$1e,$8b
		.db $55,$4a,$e5,$ea,$e0,$fe,$7f,$bc
		.db $47,$8e,$5f,$98,$20,$8a,$55,$aa
		.db $b1,$e2,$e5,$ea,$65,$32,$15,$82

StarG		.db $54,$a6,$56,$a7,$57,$a7,$0d,$ff
		.db $55,$aa,$54,$01,$9e,$fc,$79,$b2
		.db $1d,$87,$57,$a7,$57,$a6,$56,$a4
		.db $b1,$78,$fc,$8e,$03,$28,$55,$aa

StarH		.db $55,$82,$50,$98,$4e,$a7,$57,$a5
		.db $55,$92,$35,$62,$e5,$6a,$e5,$b0
		.db $4f,$9e,$3f,$71,$04,$aa,$54,$aa
		.db $bc,$7e,$ff,$e0,$e5,$ea,$45,$4a

CharSet		.block 712

.end
