
;
; SPRITE PACKAGE
; Alvin Albrecht, aralbrec@interchange.ubc.ca 10/17/2002
;
; Tab stops every 8 chars.
; See "TEST" for an example of how to use sprites.
; Once assembled, a RANDOMIZE USR 59648 will run "TEST".
;
; For ts2068/tc2048/spectrum in spectrum display mode
; (256x192 pixels, 32x24 chars, 32x24 colour)
; For quickest frame rates:
; - Use background tiles in preference to sprites.
;   Moving background tiles are at least 6 times faster
;   than moving sprites and at least 4 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.  For very large
;   animations, a mixture of small sprites for the outline and
;   background tiles (or static sprites) for the interior may help
;   to speed things up greatly.
; - 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 (256x192 mono graphic)
;   5800..5b00  attributes (32x24 colour)
;
;   7e00..7f00	interrupt vector table
;   7f01..7fff  stack
;
;   * end contended memory *
;
;   8000..801f	row 0 background bitmap hi
;   8020, 28, 30, 38 dirty area
;   8039..80fc	14 sprite data structures
;   8100..811f	row 0 background bitmap lo
;   8120..81ff	16 sprite data structures
;   8200..821f  row 0 next char struct hi
;   8220..82ff	16 sprite data structures
;   8300..831f	row 0 next char struct lo
;   8320..83ff	16 sprite data structures
;   8400..841f	row 1 background bitmap hi
;   8420, 28, 30, 38 dirty area
;   8439..84fc	14 sprite data structures
;   8500..851f	row 1 background bitmap lo
;   8520..85ff	16 sprite data structures
;   8600..861f  row 1 next char struct hi
;   8620..86ff	16 sprite data structures
;   8700..871f	row 1 next char struct lo
;   8720..87ff	16 sprite data structures
;   8800..881f	row 2 background bitmap hi
;   8820, 28, 30, 38 dirty area
;   8839..88fc	14 sprite data structures
;   8900..891f	row 2 background bitmap lo
;   8920..89ff	16 sprite data structures
;   8a00..8a1f  row 2 next char struct hi
;   8a20..8aff	16 sprite data structures
;   8b00..8b1f	row 2 next char struct lo
;   8b20..8bff	16 sprite data structures
;   8c00..8c1f	row 3 background bitmap hi
;   8c20, 28, 30, 38 dirty area
;   8c39..8cfc	14 sprite data structures
;   8d00..8d1f	row 3 background bitmap lo
;   8d20..8dff	16 sprite data structures
;   8e00..8e1f  row 3 next char struct hi
;   8e20..8eff	16 sprite data structures
;   8f00..8f1f	row 3 next char struct lo
;   8f20..8fff	16 sprite data structures
;   9000..901f	row 4 background bitmap hi
;   9020, 28, 30, 38 dirty area
;   9039..90fc	14 sprite data structures
;   9100..911f	row 4 background bitmap lo
;   9120..91ff	16 sprite data structures
;   9200..921f  row 4 next char struct hi
;   9220..92ff	16 sprite data structures
;   9300..931f	row 4 next char struct lo
;   9320..93ff	16 sprite data structures
;   9400..941f	row 5 background bitmap hi
;   9420, 28, 30, 38 dirty area
;   9439..94fc	14 sprite data structures
;   9500..951f	row 5 background bitmap lo
;   9520..95ff	16 sprite data structures
;   9600..961f  row 5 next char struct hi
;   9620..96ff	16 sprite data structures
;   9700..971f	row 5 next char struct lo
;   9720..97ff	16 sprite data structures
;   9800..981f	row 6 background bitmap hi
;   9820, 28, 30, 38 dirty area
;   9839..98fc	14 sprite data structures
;   9900..991f	row 6 background bitmap lo
;   9920..99ff	16 sprite data structures
;   9a00..9a1f  row 6 next char struct hi
;   9a20..9aff	16 sprite data structures
;   9b00..9b1f	row 6 next char struct lo
;   9b20..9bff	16 sprite data structures
;   9c00..9c1f	row 7 background bitmap hi
;   9c20, 28, 30, 38 dirty area
;   9c39..9cfc	14 sprite data structures
;   9d00..9d1f	row 7 background bitmap lo
;   9d20..9dff	16 sprite data structures
;   9e00..9e1f  row 7 next char struct hi
;   9e20..9eff	16 sprite data structures
;   9f00..9f1f	row 7 next char struct lo
;   9f20..9fff	16 sprite data structures
;   a000..a01f	row 8 background bitmap hi
;   a020, 28, 30, 38 dirty area
;   a039..a0fc	14 sprite data structures
;   a100..a11f	row 8 background bitmap lo
;   a120..a1ff	16 sprite data structures
;   a200..a21f  row 8 next char struct hi
;   a220..a2ff	16 sprite data structures
;   a300..a31f	row 8 next char struct lo
;   a320..a3ff	16 sprite data structures
;   a400..a41f	row 9 background bitmap hi
;   a420, 28, 30, 38 dirty area
;   a439..a4fc	14 sprite data structures
;   a500..a51f	row 9 background bitmap lo
;   a520..a5ff	16 sprite data structures
;   a600..a61f  row 9 next char struct hi
;   a620..a6ff	16 sprite data structures
;   a700..a71f	row 9 next char struct lo
;   a720..a7ff	16 sprite data structures
;   a800..a81f	row 10 background bitmap hi
;   a820, 28, 30, 38 dirty area
;   a900..a91f	row 10 background bitmap lo
;   aa00..aa1f  row 10 next char struct hi
;   ab00..ab1f	row 10 next char struct lo
;   ac00..ac1f	row 11 background bitmap hi
;   ac20, 28, 30, 38 dirty area
;   ad00..ad1f	row 11 background bitmap lo
;   ae00..ae1f  row 11 next char struct hi
;   af00..af1f	row 11 next char struct lo
;   b000..b01f	row 12 background bitmap hi
;   b020, 28, 30, 38 dirty area
;   b100..b11f	row 12 background bitmap lo
;   b200..b21f  row 12 next char struct hi
;   b300..b31f	row 12 next char struct lo
;   b400..b41f	row 13 background bitmap hi
;   b420, 28, 30, 38 dirty area
;   b500..b51f	row 13 background bitmap lo
;   b600..b61f  row 13 next char struct hi
;   b700..b71f	row 13 next char struct lo
;   b800..b81f	row 14 background bitmap hi
;   b820, 28, 30, 38 dirty area
;   b900..b91f	row 14 background bitmap lo
;   ba00..ba1f  row 14 next char struct hi
;   bb00..bb1f	row 14 next char struct lo
;   bc00..bc1f	row 15 background bitmap hi
;   bc20, 28, 30, 38 dirty area
;   bd00..bd1f	row 15 background bitmap lo
;   be00..be1f  row 15 next char struct hi
;   bf00..bf1f	row 15 next char struct lo
;   c000..c01f	row 16 background bitmap hi
;   c020, 28, 30, 38 dirty area
;   c100..c11f	row 16 background bitmap lo
;   c200..c21f  row 16 next char struct hi
;   c300..c31f	row 16 next char struct lo
;   c400..c41f	row 17 background bitmap hi
;   c420, 28, 30, 38 dirty area
;   c500..c51f	row 17 background bitmap lo
;   c600..c61f  row 17 next char struct hi
;   c700..c71f	row 17 next char struct lo
;   c800..c81f	row 18 background bitmap hi
;   c820, 28, 30, 38 dirty area
;   c900..c91f	row 18 background bitmap lo
;   ca00..ca1f  row 18 next char struct hi
;   cb00..cb1f	row 18 next char struct lo
;   cc00..cc1f	row 19 background bitmap hi
;   cc20, 28, 30, 38 dirty area
;   cd00..cd1f	row 19 background bitmap lo
;   ce00..ce1f  row 19 next char struct hi
;   cf00..cf1f	row 19 next char struct lo
;   d000..d01f	row 20 background bitmap hi
;   d020, 28, 30, 38 dirty area
;   d100..d11f	row 20 background bitmap lo
;   d200..d21f  row 20 next char struct hi
;   d300..d31f	row 20 next char struct lo
;   d400..d41f	row 21 background bitmap hi
;   d420, 28, 30, 38 dirty area
;   d500..d51f	row 21 background bitmap lo
;   d600..d61f  row 21 next char struct hi
;   d700..d71f	row 21 next char struct lo
;   d800..d81f	row 22 background bitmap hi
;   d820, 28, 30, 38 dirty area
;   d900..d91f	row 22 background bitmap lo
;   da00..da1f  row 22 next char struct hi
;   db00..db1f	row 22 next char struct lo
;   dc00..dc1f	row 23 background bitmap hi
;   dc20, 28, 30, 38 dirty area
;   dd00..dd1f	row 23 background bitmap lo
;   de00..de1f  row 23 next char struct hi
;   df00..df1f	row 23 next char struct lo
;
;   df20..e610  sprite code
;
;   efc7..f000	various data structures
;   f020..ffff	horizontal rotate tables
;
;
; Important Address Layouts:
;
;   DFILE PIXELS (32x24 chars, 256x192 pixels):
;
;     7   6   5   4   3   2   1   0
;   +---+---+---+---+---+---+---+---+
;   | 0 | 1 | 0 | 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
;
;
;   DFILE ATTRIBUTES (32x24 colour)
;
;     7   6   5   4   3   2   1   0
;   +---+---+---+---+---+---+---+---+
;   | 0 | 1 | 0 | 1 | 1 | 0 | B | B |    MSB
;   +---+---+---+---+---+---+---+---+
;
;     7   6   5   4   3   2   1   0
;   +---+---+---+---+---+---+---+---+
;   | L | L | L | C | C | C | C | C |    LSB
;   +---+---+---+---+---+---+---+---+
;
;   BB    = screen block
;   LLL   = character line within block
;   CCCCC = horizontal char position
;
;
;   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 | 0 | C | C | C | C | C |    LSB
;   +---+---+---+---+---+---+---+---+
;
;   RRRRR  = character row (0..23)
;   CCCCC  = character column (0..31)
;   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 | 0 | 1 | C | C | 0 | 0 | 0 |    LSB
;   +---+---+---+---+---+---+---+---+
;
;   CC     = character column / 8
;
;     non-zero indicates chars within
;     row RRRRR, columns CC000..CC111
;     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 |      colour       | colour attribute for this char
;   +-------------------+ 
;
;
;   Sprite Structure
;
;   +-------------------+
; 0 | row coordinate    | 0..23 (if on screen)
;   +-------------------+
; 1 | col coordinate    | 0..31 (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
;   +-------------------+
;
;
;   Colour Attribute
;
;     7   6   5   4   3   2   1   0
;   +---+---+---+---+---+---+---+---+  $80 is transparent
;   | F | B | P | P | P | I | I | I |  for sprites only
;   +---+---+---+---+---+---+---+---+
;
;   F   = flash
;   B   = bright
;   PPP = paper \ 0=black, 1=blue, 2=red, 3=magenta,
;   III = ink   / 4=green, 5=cyan, 6=yellow, 7=white
;
;
;   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 pairs.  The column bitdef is
;   defined similarly to a UDG, but two bytes are used for each vertical
;   pixel rather than one.  As with the UDG, the first byte defines the
;   pixel graphic.  Unlike the UDG, the next byte defines the mask, where
;   ones indicate the corresponding bit underneath the sprite will show
;   through.  The rest of the vertical pixels are defined similarly.
;   Colour is assigned on a per-char basis, with the colour $80 reserved
;   to indicate "no colour" (ie, take on the colour underneath the sprite).
;   During sprite creation, each column is assigned a single solid colour.
;   To change the colour of individual chars, see "IterateSprChar".
;   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 32x24 resolution.  The PrintAt subroutine allows manipulation of
;   the text in the background.  Changes in the background are
;   automatically reflected on the display.  Background UDGs define all
;   8 bytes of the graphic bitmap (as usual) but are then followed by a
;   single byte colour attribute.  $80 is not a special colour for
;   background tiles.
;   ALL 16-BYTE BACKGROUND UDG DEFINITIONS CANNOT CROSS 256 BYTE BOUNDARIES
;   AND MUST RESIDE IN THE 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
;   IterateSprChar	iterates over all chars in sprite and calls hook
;   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 $efc7
GreyBlock	.byte $55,$aa,$55,$aa,$55,$aa,$55,$aa,$38
		.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 2			; 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
tempcolour	.byte 0


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 $df20
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.
		; for beep sound, twiddle the beep bits while waiting in the
		; spin loop (see TEST for what the spin loop looks like)
		
		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 : 130 + 10.42C + 258.25D + 338N + 29S + 1434A - 822Af
;
;   C = # chars on screen = 32*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,$8020		; 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,8
		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
		cp $40
		jp c, arealp		; if column still < 32, repeat arealp
reenter		ld l,$20		; 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 5,l			; hl = display list, background bitmap HI
		ld a,(hl)
		cp $80
		call nc, drawchar	; if char marked for update, draw it
		inc e			; move to next column
		inc l			; hl = next column display list, bgnd bitmap HI
		ld a,(hl)
		cp $80
		call nc, drawchar
		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
		inc e
		inc l
		ld a,(hl)
		cp $80
		call nc, drawchar
		inc e
		inc l
		ld a,(hl)
		cp $80
		call nc, drawchar
		inc e
		inc l
		ld a,(hl)
		cp $80
		call nc, drawchar
		inc e
		inc l
		ld a,(hl)
		cp $80
		call nc, drawchar
		inc e
		inc l
		ld a,(hl)
		cp $80
		call nc, drawchar
		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 5,l			; hl = display list, dirty area
		inc l			; advance past 8th char in last dirty area
		ld a,l
		cp $40
		jp c, arealp		; if column still < 32
		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
		inc c

		ld a,(bc)		; store pending colour in temporary place
		ld (tempcolour),a	; annoying flicker was observed if more than one
					; colour is printed to the same square during
					; update

		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,d		; fix screen address to point at top of char
		and $f8
		ld d,a
		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

colourit	pop hl			; hl = char struct + 9
		inc l
		inc l
		inc l				; hl = char struct + 12

		ld a,(hl)
		cp $80
		jr z, clear			; don't colour if transparent
		ld (tempcolour),a		; store pending final colour

clear		dec l
		dec l			; hl = char struct + 10
		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

		ld a,d		; no more chars to print, time to colour square
		xor $85
		rrca
		rrca
		rrca
		ld d,a		; attribute address computed
		ld a,(tempcolour)
		ld (de),a		; store final colour
		ld a,d
		xor $50
		rlca
		rlca
		rlca
		ld d,a		; de back to top of char again
		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 colourit


;
; 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 32
		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,$8020
		ld de,13
malloclp	xor a
		push hl			; each pass through loop allocates
		ld b,14			;   62 sprite data structures
		ld l,$39
		call InitBlk		; these data structures must reside
		pop hl			;   in upper 32k
		inc h
		push hl
		ld b,16
		call InitBlk
		pop hl
		inc h
		push hl
		ld b,16
		call InitBlk
		pop hl
		inc h
		push hl
		ld b,16
		call InitBlk
		pop hl
		inc h
		ld a,h
		cp $a8
		jp c, malloclp		; steal memory in rows 0..9 inclusive

		; 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
;        A' = colour attribute for column

; exit : IX = sprite structure address
;        carry for success
; time : 377 + 394*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
		inc l
		inc l
		ex af,af'
		ld (hl),a
		ex af,af'
		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
;        A' = colour attribute for entire column
; exit : carry for success
; time : 321 + 498*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
		inc l
		inc l
		ex af,af'
		ld (hl),a
		ex af,af'
		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


; Iterate Sprite Chars
;
; Chars in the sprite are iterated in column major order.
;
; enter: IX = sprite structure address
;        IY = user supplied subroutine address
;             user hook can use all registers safely
;             hook routine entered with hl = char struct + 0
; time : 53 + (87 + hook)*rows*cols

IterateSprChar	ld a,6
			.db $dd \ add a,l
			ld l,a
			.db $dd \ ld a,h
			ld h,a
iterlp		ld a,(hl)
			or a
			ret z
			inc l
			ld l,(hl)
			ld h,a
			push hl
			call JPIY
			pop hl
			jp iterlp

JPIY			jp (iy)


; 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 32			; is current col position on screen?
		jr nc, skipcol		; no - skip col
		ld l,a
		set 7,(hl)		; mark char as needing update
		and $18
		or $20
		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)
		ld (ix+5),e		; store new vertical rotation
		add a,a
		neg
		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 32
		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 $18
		or $20
		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..31)
;        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 $18
		or $20
		ld l,a
		ld (hl),$ff
		ret


; SCREEN$(row,col)
;
; enter:  A = row position (0..23)
;         L = col position (0..31)
; 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 $


;
; (A rather badly written) Sprites Test
;
; Ten 4x4 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,15,0,09,18,0,09,21,0,09,24,0,09,27,0
		.db 12,15,2,12,18,2,12,21,2,12,24,2,12,27,2
		.db 15,15,4,15,18,4,15,21,4,15,24,4,15,27,4
		.db 18,15,6,18,18,6,18,21,6,18,24,6,18,27,6

message1	.byte "FRAME COUNT:",0
message2	.byte "TARGET UPDATE RATE:",0
message3	.byte "0",0
Here		.equ $			; make sure 9-byte chars do not
CharSet	.equ ((Here+255)/256)*256	; cross 256 byte boundaries
CHARS		.equ 15360		; char set in rom -- fix for TC2048

		.org $e900
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, plus add colour
		ld hl,CHARS+256
		ld de,CharSet
		ld a,28				; don't want to cross 256-byte
		call charlp				; boundary so only 28 chars in
		ld hl,CHARS+480			; each 256 bytes
		ld de,CharSet+256
		ld a,28
		call charlp
		jr around

charlp	ld bc,8
		ldir
		ex de,hl
		ld (hl),$39				; blue on white
		ex de,hl
		inc de
		dec a
		jp nz, charlp
		ret

		; print static part of messages
around	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,$0401
initloop	push af
		push hl
		push bc
		ld a,$2b		 ; all sprites magenta on cyan
		ex af,af'
		ld de,col1
		call CreateSpr
		ld de,col2
		call AddColSpr
		ld de,col3
		call AddColSpr
		ld de,col4
		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
		pop 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 32
		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 hl,StarH
		cp 7
		jr z,yeah
		ld de,StarA
		ld l,a
		ld h,0
		add hl,hl
		add hl,hl
		push hl
		add hl,hl
		add hl,hl
		add hl,hl
		add hl,de
		pop de
		add hl,de
yeah		ex de,hl
		ld l,c
		ld a,b
		call PrintAt
		ld hl,9
		add hl,de
		ex de,hl
		inc c
		ld l,c
		ld a,b
		call PrintAt
		ld hl,9
		add hl,de
		ex de,hl
		dec c
		inc b
		ld l,c
		ld a,b
		call PrintAt
		ld hl,9
		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
		rra
		rra
		rra
		rra
		and $0f
		cp $0a
		jr c,isdigit1
		add a,'A'-10-'0'
isdigit1	add a,'0'
		ld (message3),a
		ld hl,message3
		ld b,1
		ld c,14
		call printmsg
		pop af
		and $0f
		cp $0a
		jr c, isdigit2
		add a,'A'-10-'0'
isdigit2	add a,'0'
		ld (message3),a
		ld hl,message3
		ld b,1
		ld c,15
		call printmsg
		jp loop

printmsg	ld a,(hl)
		or a
		ret z
		inc hl
		push hl
		ld de,CharSet-288
		cp ' '+28
		jr c, conti
		ld de,CharSet-284
conti		ld l,a
		ld h,0
		push hl
		add hl,hl
		add hl,hl
		add hl,hl
		add hl,de
		pop de
		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 $eb00
		.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,$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 0,$ff,0,$ff,0,$ff,0,$ff,0,$ff,0,$ff,0,$ff,0,$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 $ec00
		.db 0,$ff,0,$ff,0,$ff,0,$ff,0,$ff,0,$ff,0,$ff
col3		.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,$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

col4		.db 0,$ff,0,$ff,0,$ff,0,$ff,0,$ff,0,$ff,0,$ff,0,$ff
		.db 0,$ff,0,$ff,0,$ff,0,$ff,0,$ff,0,$ff,0,$ff,0,$ff
		.db 0,$ff,0,$ff,0,$ff,0,$ff,0,$ff,0,$ff,0,$ff,0,$ff
		.db 0,$ff,0,$ff,0,$ff,0,$ff,0,$ff,0,$ff,0,$ff,0,$ff


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

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

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

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

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

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

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

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

.end
