[comp.sys.handhelds] SPRITE - An HP-48 Sprite Drawer

bson@rice-chex.ai.mit.edu (Jan Brittenson) (11/16/90)

   OK, here's my sprite drawer if you're interested. First a warning:
this program will happily scramble your memory if given improper
arguments. This generally means, but is not limited to, specifying
coordinates outside the GROB.

   I will post the source code only. It is copyrighted by me, but you
are hereby granted the right to use it for whatever non-commercial
purposes you desire. For other purposes, consult me. Of course no
responsibility or liability is taken. You're on your own here, should
your beloved HP go down in flames, or you end up requiring life-long
psychiatric care.



SPRITE

The sprite drawer initially reads four arguments off the stack:

	4: destination.grob
	3: row.short
	2: col.short
	1: sprite.string

   The row (Y) and column (X) are read into the Y and X registers, and
the IP is set to the first nibble of the string, where execution will
begin.

The drawer maintains the following of registers:

	reg bits  description

	 X   12	- current column (0 is leftmost) 0-#fff
	 Y   12	- current row (0 is uppermost) 0-#fff
         A    4 - angle 0-15
	 C   20	- collision counter 0-#fffff
	 G    3 - current GROP, graphical operator, 0,1,2,4
	 F    3 - flags F.C, F.PP, F.PC
	 IP  20 - current sprite address


[Initial values: A=0, C=0, F.*=0, G=1]

The F register is divided into three bits:

	F.C	- collision detection off/on (0/1)
	F.PP	- push point on stack when done off/on (0/1)
	F.PC	- push collision counter on stack when done off/on (0/1)


   The A register contains the angle offset. It consitutes an offset
added to all drawing and moving commands to deduct the effective
angle. The angle is one of 16:

			5 3
		       6 4 2
		      7	\|/ 1
		       8- -0
		      9	/|\ f
		       a c e
			b d


   The odd angles are halfway between the even ones. The angle 5 is
halfway between 4 and 6 (112.5 degrees).

During execution the following commands make sense; all others are
ignored.


       opcode	mnemonic	decriptions

	1g	G=g		Set GROP to g: 1=BIS, 2=XOR, 4=BIC
	2	F.C=1		Enable collision detection
	3	F.C=0		Disable collision detection
	4na	MOVE n,a	Move n (1-15) pixels, angle a
	Enna	MOVE nn,a	Move nn (1-255) pixels, angle a
	7a	DRAW 1,a	Draw 1 pixel, angle a
	8na	DRAW n,a	Draw n (1-15) pixels, angle a
	Dnna	DRAW nn,a	Draw nn (1-255) pixels, angle a
	B	SETPT		Set pixel at current X,Y
	90	F.PC=1		Push collision counter when done
	91	F.PP=1		Push Y,X when done
	92	POP XY		Pop new X,Y off stack
	93	POP A		Pop new A off stack
	c	END


   The drawing and moving commands start at the current point, and
move the specified number of pixels in the specified direction. The
drawing commands set the pre-draw current point, and end up with the
current point set to the first pixel following the last pixel set. In
other words, the command DRAW 3,2 (822) will do the following:


			      O 4: move one pixel and leave the point
			     /
			    * 3: move one pixel and set the point
			   /
			  * 2: move one pixel and set the point
			 /
			* 1: set the current point


   The pixel is set according to the current GROP (G register). The
same is true for SETPT as well, which simply sets the current point
without moving. If collision detection is enabled, the point is tested
and the collision counter updated before any graphical operation is
applied. When drawing, collision is checked for the first and up to
but not including the final point (i.e. 1-3 would be checked in the
example above). SETPT always checks for collision detection if
enabled.

I hope this brief explanation is sufficient.

O  /
 \/
 /\ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
O  \

;; +
;; SPRITE -- HP-48 sprite drawer
;;
;;
;; In:
;;   4:dest.grob
;;   3:row.short
;;   2:col.short
;;   1:sprite.string
;;
;; Out:
;;   any or none
;;
;;
;; Register usage:
;;
;;   d1   - current GROB address
;;   d0   - current sprite address
;;   r0.x - GROB cols (X max + 1)
;;   r1.x - GROB rows (Y max + 1)
;;   r3.x - current col (X), 0-#fff (0-4095)
;;   r4.x - current row (Y), 0-#fff (0-4095)
;;   r3.3 - rotational angle 0-15
;;   b.s  - current mask (1,2,4,8)
;;   r3.4:8 - collision counter
;;   r3.9 - current GROP:
;;		1 - OR
;;		2 - XOR
;;		4 - bic
;;
;;   r3.13:15 - GROB row width, in nibbles
;;
;;   d.s  - current flags:
;;		0 - collision detection enabled
;;		1 - exit
;;		2 - push collision counter when done
;;		3 - push end point when done
;;
;;   r2.a - relocation constant
;;   
;;
;; bson@ai.mit.edu, Nov 8, 1990
;;
;; Copyright (c) 1990, Jan Brittenson
;;
;; This program is distributed in the hope that it will be useful, but
;; WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. You may freely
;; use, modify, and redistribute it for any noncommercial purpose, given
;; that this notice is not removed or changed.
;;
;;
;;--

get2shorts=#3f5d
getshort=#6641
restore_regs=#67d2
save_regs=#679b
error_too_few_args=#18cc7
error_bad_arg_type=#18cb7
pushshort_r0=#6537
pushshort_r0r1=#6529
rplret=#71be

	data.b	'H'
	data.b	'P'
	data.b	'H'
	data.b	'P'
	data.b	'4'
	data.b	'8'
	data.b	'-'
	data.b	'D'
	data.a	#2dcc
begin:	data.a	end-begin

sprite:	call.3	reloc
reloc:	pop.a	c
	move.w	c,r2		; R2 = logical reloc

	move.a	@d1,c
	brnz.a	c,L_1000
	jump.3	sprite_insuff_args
L_1000:	add.a	5,d1
	inc.a	d
	swap.a	c,d1
	push.a	c
	move.a	@d1,a
	move.p5	#2a2c,c		; String
	breq.a	c,a,L_1001
	jump.3	sprite_bad_argtype
L_1001:	add.a	10,d1		; Skip type and length
	pop.a	c
	swap.a	c,d1
	push.a	c		; Push string addr

	call.3	pop_xy		; Get row, col from stack

	move.a	@d1,c
	brnz.a	c,L_1002
	jump.3	sprite_insuff_args
L_1002:	add.a	5,d1
	inc.a	d
	swap.a	c,d1
	push.a	c
	move.a	@d1,a
	move.p5	#2b1e,c		; GROB
	breq.a	c,a,L_1003
	jump.3	sprite_bad_argtype
L_1003:	add.a	10,d1
	move.a	@d1,a		; Get GROB height
	move.w	a,r1		; R1.x = GROB height
	add.a	5,d1
	move.a	@d1,a		; Get GROB width
	move.w	a,r0		; R0.x = GROB width
	pop.a	c
	swap.a	c,d1
	push.a	c

	call.a	save_regs
	pop.a	c
	move.a	c,d1
	add.a	5,d1		; D1 = GROB data
	pop.a	c
	move.a	c,d0		; D0 = string address

	call.3	sprite_init	; Initialize reentrant variables

; Main loop

sprite_main:
	call.3	sprite_exec1	; Execute one command
	rln.w	d
	move.p	d,c
	rrn.w	d
	brbc	1,c,sprite_main

; Exit

	move.s	d,c
	move.w	c,r0
	call.a	restore_regs
	move.w	r0,c
	rln.w	c
	push.a	c
	move.a	c,a

	brbc	3,a,L_301		; Don't push final point
	clr.a	c
	move.x	r4,c
	move.w	c,r0
	move.x	r3,c
	move.w	c,r1
	call.a	pushshort_r0r1		; Push in order: row, col
L_301:
	pop.a	c
	move.a	c,a
	brbc	2,a,L_300		; Don't push collision counter
	move.w	r3,c
	rrn.w	c
	rrn.w	c
	rrn.w	c
	rrn.w	c
	move.w	c,r0
	call.a	pushshort_r0		; Push: coll count
L_300:
	jump.a	rplret


; Various error messages

sprite_bad_argtype:
	jump.a	error_bad_arg_type

sprite_insuff_args:
	jump.a	error_too_few_args


; Get X and Y from stack

pop_xy:
	call.a	get2shorts	; C=lev1.short, A=lev2.short
	move.x	a,r3		; R3.x = cur row
	move.x	c,r4		; R4.x = cur col
	ret

; Reset to default values, except for variables from stack.

sprite_init:
	move.w	r2,a
	move.p5	grob_base,c
	add.a	a,c
	swap.a	c,d1
	move.a	c,@d1			; Save GROB addr in grob_base
	swap.a	c,d1

	move.w	r3,a			; Angle, coll ctr, GROP, all = 0
	clr.w	c
	or.x	a,c

	move.w	r0,a			; A.x = GROB cols
	brbs	0,a,L_1			; Not a byte
	brbs	1,a,L_1			; Not a byte
	brbc	2,a,L_2			; A byte
L_1:
	add.x	8,a			; Adjust to next byte
L_2:
	srb.x	a			; Turn into nibble count
	srb.x	a
	clrb	0,a			; Always even # of nibbles

	rrn.w	a			; Move into nibbles 13,14,15
	rrn.w	a
	rrn.w	a
	move.1	13,p
	or.p	a,c
	inc.1	p
	or.p	a,c
	inc.1	p
	or.p	a,c
	move.w	c,r3

	move.1	0,p

	move.w	r4,a			; A.x = row#, C.x = col#
	call.3	sprite_mapxy		; C.a = addr, A.0 = mask
	rrn.w	a
	move.s	a,b			; B.s = cur mask

	swap.a	a,d1
	add.a	c,a
	swap.a	a,d1			; D1 = cur addr

	move.p1	1,c			; Default GROP = OR
	call.3	set_grop

	clr.s	d			; No flags enabled
	ret

; Map row (A.x) and col (C.x) to nibble offset and mask.
; Set C.a to addr, and A.1 to mask.

sprite_mapxy:
	push.a	c
	move.x	a,c
	move.x	c,d		; D.x = row #
	move.w	r3,c
	clr.b	c
	rln.w	c
	rln.w	c
	rln.w	c		; C.a = row size in nibbles
	clr.a	b
	brz.x	d,L_4
L_3:
	add.a	c,b
	dec.x	d
	brnz.x	d,L_3
L_4:
	pop.a	c
	clr.a	d
	move.x	c,d		; D.a = col #
	srb.x	d
	srb.x	d		; D.a = nibble #
	swap.a	c,d
	add.a	c,b		; B.a = addr
	swap.a	c,d
	clrb	2,c
	clrb	3,c
	move.p	c,d		; D.a = col #
	clr.a	a
	inc.a	a		; A.a = 1
	brz.p	d,L_5
L_6:
	add.a	a,a
	dec.p	d
	brz.p	d,L_6
L_5:
	move.a	b,c		; C.a = nibble #
	ret

; Set current pixel

spr_x_setpix:
	call.3	get_grop		; C.0 = GROP
	move.s	d,c			; C.1 = GROP, C.0 = run flags
	rln.w	c

	brbc	0,c,L_428		; No collision detection - skip
	move.s	@d1,c			; C.15 = Current GROB nibble
	and.s	b,c			; Test if collision
	brz.s	c,L_428			; Nope - skip
	move.w	r3,a
	rrn.w	a
	rrn.w	a
	rrn.w	a
	rrn.w	a
	inc.x	a			; Yep - increment counter
	rln.w	a
	rln.w	a
	rln.w	a
	rln.w	a
	move.w	a,r3
L_428:
	move.s	@d1,c			; C.15 = Current GROB nibble
	brbc	4,c,L_4281		; GROP != OR
	or.s	b,c			; C.15 = old OR mask
	brcc	L_4299
L_4281:	brbc	5,c,L_4282		; GROP != XOR
	move.s	b,a
	not.s	a
	and.s	c,a			; A.s = !B.s & C.s
	not.s	c
	and.s	b,c			; C.s = B.s & !C.s
	or.s	a,c			; C.s = !B.s & C.s | B.s & !C.s
	brcc	L_4299
L_4282:	brbc	6,c,L_42991		; GROP != BIC
	move.s	b,a
	not.s	a			; A.s = !mask
	and.s	a,c			; C.s = !mask & C.s
L_4299:
	move.s	c,@d1			; Write back
L_42991:
	ret

; Execute one sprite command

sprite_exec1:
	clr.a	a
	move.p	@d0,a			; A.a = nibble
	add.a	1,d0
	move.p5	sprite_exec_table,c

; Jump indirect table.
; A.a = entry#, C = table offset

spr_exec:
	move.a	c,b
	move.w	r2,c			; C.a = reloc
	add.a	b,c			; C.a = addr
	move.a	a,b			; B=entry#
	add.a	a,a
	add.a	a,a			; A *= 4
	add.a	b,a			; A *= (4+1)
	add.a	c,a			; A = table addr
	swap.a	a,d0
	move.a	@d0,c
	swap.a	a,d0
	move.a	c,b			; B = code offset
	move.w	r2,c
	add.a	b,c			; C = code addr
	jump.a	c

; END - terminate

spr_x_end:
	rln.w	d			; Get flag word
	move.p	d,c
	setb	1,c			; Set bit #1
	move.p	c,d
	rrn.w	d

; NOP - no operation

spr_x_nop:
	ret

; ENA COLL - enable collision detection

spr_x_ena_coll:
	rln.w	d			; Get flag word
	move.p	d,c
	setb	0,c			; Set bit 0
L_8:
	move.p	c,d
	rrn.w	d
	ret	

; DIS COLL - disable collision detection

spr_x_dis_coll:
	rln.w	d			; Get flag word
	move.p	d,c
	clrb	0,c			; Clear bit 0
	brnz.s	b,L_8

; PUSH/POP - various PUSH/POP commands

spr_x_push:
	clr.a	a
	move.1	@d0,a			; Next nibble
	add.a	1,d0
	move.p5	push_table,c
	jump.3	spr_exec		; Jump indirect

; PUSH COLL - push collision count when done

spr_x_push_coll:
	setb	2,c			; Set bit 2
	brnz.p	c,L_8

; PUSH POINT - push last point when done

spr_x_push_pt:
	setb	3,c			; Set bit 3
	brnz.p	c,L_8

; POP XY - Get new X and Y coordinates from stack

spr_x_pop_xy:
	call.3	swap_regs
	call.3	pop_xy			; Pop row, col off stack
	call.3	swap_regs

	move.w	r3,c			; C.x = col #
	move.w	r4,a			; A.x = row #
	call.3	sprite_mapxy		; C.a = addr, A.0 = mask
	rrn.w	a
	move.s	a,b			; B.s = new mask

	push.a	c			; Save GROB offset
	move.w	r2,a			; A.a = reloc
	move.p5	grob_base,c		
	add.a	c,a			; C.a = &grob_base
	swap.a	a,d1
	move.a	@d1,a			; A.a = grob_base
	pop.a	c
	add.a	c,a			; A.a = grob_base + new offset
	move.a	a,d1			; D1 = new GROB address
	ret

; POP ANGLE - Get new rotation from stack

spr_x_pop_rot:
	call.3	swap_regs
	call.a	getshort		; A.a = angle
	call.3	swap_regs

	move.1	3,p
	rln.w	a
	rln.w	a
	rln.w	a			; A.3 = angle
	move.p	a,r3			; R3.3 = angle
	move.1	0,p
	ret

; MOVE n,m - move n (0-15) pixels, angle m

spr_x_move:
	call.3	get_grop		; C.0 = GROP
	move.s	d,c			; C.0 = run flags, C.1 = GROP
	rln.w	c
	push.a	c
	clrb	0,c			; Disable collision detection
	rrn.w	c
	move.s	c,d
	clr.p	c
	call.3	set_grop		; GROP = 0
	call.3	spr_x_drawn		; Draw with null GROP
	pop.a	c			; C.1 = GROP, C.0 = run flags
	rrn.w	c
	move.s	c,d			; Restore run flags
	jump.3	set_grop		; Restore GROP


; MOVE nn,m - move nn (0-255) pixels, angle m

spr_x_movenn:
	call.3	get_grop		; C.0 = GROP
	move.s	d,c			; C.0 = run flags, C.1 = GROP
	rln.w	c
	push.a	c
	clrb	0,c			; Disable collision detection
	rrn.w	c
	move.s	c,d
	clr.p	c
	call.3	set_grop		; GROP = 0
	call.3	spr_x_drawnn		; Draw with null GROP
	pop.a	c			; C.1 = GROP, C.0 = run flags
	rrn.w	c
	move.s	c,d			; Restore run flags
	jump.3	set_grop		; Restore GROP

	
; Adjust row and column according to row/col increments.
; Row: A.x, Col: C.x, Adjustment: B.x, Increments: A.3:4
; Uses: D.x

adjrowcol:
	swap.a	c,a
	move.x	c,d
	swap.a	c,a
	swap.a	c,b
	push.a	c
	swap.a	b,c
	rrn.w	a

	brbc	12,a,L_222		; Row is not half-angle
	srb.x	b			; Row is half-angle
L_222:	brbc	8,a,L_20		; No row movement, adjust col
	brbs	9,a,L_21		; Moving up
	neg.x	b			; Up: use negative offset
L_21:	swap.a	c,d
	add.x	b,c			; Down: add to row number
	swap.a	c,d
L_20:
	swap.a	c,b
	pop.a	c
	push.a	c
	swap.a	c,b

	brbc	14,a,L_223		; Col is not half-angle
	srb.x	b			; Col is half-angle
L_223:	brbc	10,a,L_221		; No col adjustment
	brbc	11,a,L_23		; Moving left
	neg.x	b			; Left: use negative col offset
L_23:	add.x	b,c			; Right: add to col #
L_221:
	swap.a	c,b
	pop.a	c
	swap.a	c,b
	rln.w	a
	swap.a	c,d
	move.x	c,a
	swap.a	c,d
	ret

; Fetch nibble and add to rotational angle.
; Map to incrementation bits, and store them in A.3:4
; Clears A.x and wrecks C.w

spr_fetch_angle:
	move.1	0,p
	clr.a	a
	move.1	@d0,a			; A.a = angle
	add.a	1,d0
	move.w	r3,c			; C.3 = rotation
	srn.a	c
	srn.a	c
	srn.a	c
	add.p	c,a
	add.a	a,a			; Nibbles to bytes

	move.w	r2,c
	add.a	c,a
	move.p5	angle_table,c
	add.a	a,c			; C.a = byte pointer
	swap.a	c,d0
	move.b	@d0,a			; A.b = angle bits
	swap.a	c,d0
	sln.w	a
	sln.w	a
	sln.w	a			; A.3:4 = angle bits
	ret

; GROP - set GROP

spr_x_grop:
	move.1	9,p
	move.w	r3,a
	move.p	@d0,a			; A.9 = nibble
	move.p1	3,c			; C.9 = 3
	and.p	c,a			; Mask low 2 bits
	move.w	a,r3
	move.1	0,p
	ret

; General GROP = C.0
; Trashes C.9

set_grop:
	move.1	c,0,p			; P = C.0
	move.1	p,c,9			; C.9 = P
	move.1	9,p
	move.p	c,r3			; R3.9 = GROP
	move.1	0,p
	ret
	
; General C.0 = GROP
; Trashes C.9

get_grop:
	move.1	9,p
	move.p	r3,c			; C.9 = GROP
	move.1	c,9,p			; P = C.9
	move.1	p,c,0			; C.0 = P
	move.1	0,p
	ret

; DRAW nn,m - draw nn pixels long sprite

spr_x_drawnn:
	clr.x	c
	move.b	@d0,c
	add.a	2,d0
	move.x	c,b			; B.x = C.x = sprite length
	brcc	spr_x_draw_b

; DRAW1 - draw 1-pixel sprite

spr_x_draw1:
	move.p1	1,c			; One pixel long
	brnz.p	c,spr_x_draw_main	; Go draw

; DRAWN - draw n-pixel sprite

spr_x_drawn:
	move.1	@d0,c			; # of pixels of length
	add.a	1,d0

; Draw sprite of length C.1. Get nibble indicating angle and
; add basic rotation. In the draw loop, registers are
; used as follows:
; 
;   B.x - length (1-10h)
;   D.a - row width, in nibbles (from R3.13:15)
;   A.x - collision counter
;   A.3 - increment flags
;     	12 - row movement on/off (0/1)
;	13 - row movement up/down (0/1)
;	14 - col movement on/off (0/1)
;	15 - col movement left/right (0/1)
;   C.0 - D.s run flags
;   C.1 - GROP
;   C.2 - half-angle increment flags
;	 8 - row is half-angle
;	 9 - row half-angle toggle
;	10 - col is half-angle
;	11 - col half-angle toggle
;
;   The rest of the registers are used in their global role. E.g.
;   B.s is the current mask.
;

spr_x_draw_main:
	move.1	0,p
	clr.x	b
	move.p	c,b			; B.x = # of pixels

; Alternative entry, B.x = sprite length

spr_x_draw_b:
	call.3	spr_fetch_angle		; A.3:4 = angle bits, A.x = 0

	move.w	r3,c			; C.x = col number
	move.x	r4,a			; A.x = row number
	call.3	adjrowcol		; Adjust C.x,A.x by B.x, via A.3:4
	move.x	c,r3			; Update col
	move.x	a,r4			; Update row

	move.w	r3,c
	rrn.w	c
	rrn.w	c
	rrn.w	c
	rrn.w	c
	move.x	c,a			; A.x = collision counter

	move.w	r3,c
	clr.b	c
	rln.w	c
	rln.w	c
	rln.w	c
	move.a	c,d			; D.a = width, in nibbles

	brbs	13,a,L_311		; Moving down, if any
	neg.a	d			; Use negative row width if up
L_311:
	call.3	get_grop		; C.0 = GROP

	move.s	d,c
	rln.w	c			; C.0 = run flags, C.1 = GROP

	swap.a	c,a
	move.1	c,4,p
	swap.a	c,a
	move.1	p,c,2			; C.xs = A.4 = half-angle flags
	move.1	0,p


; Main draw loop.
; Alter pixel, move, and iterate.

sprite_draw:
	brbc	0,c,L_28		; No collision detection - skip

	move.s	@d1,c			; C.15 = Current GROB nibble
	and.s	b,c			; Test if collision
	brz.s	c,L_28			; Nope - skip
	inc.x	a			; Yep - increment counter
L_28:
	move.s	@d1,c			; C.15 = Current GROB nibble
	brbc	4,c,L_281		; GROP != OR
	or.s	b,c			; C.15 = old OR mask
	brcc	L_299
L_281:	brbc	5,c,L_282		; GROP != XOR
	move.s	b,a
	not.s	a
	and.s	c,a			; A.s = !B.s & C.s
	not.s	c
	and.s	b,c			; C.s = B.s & !C.s
	or.s	a,c			; C.s = !B.s & C.s | B.s & !C.s
	brcc	L_299
L_282:	brbc	6,c,L_2991		; GROP != BIC
	move.s	b,a
	not.s	a			; A.s = !mask
	and.s	a,c			; C.s = !mask & C.s
L_299:
	move.s	c,@d1			; Write back
L_2991:
	brbc	12,a,L_30		; No row movement - move col
	brbc	8,c,L_100		; Row is not half-angle, simply add
	brbs	9,c,L_101		; Move and clear toggle
	setb	9,c			; Move next time around
	brcc	L_30			; Move col
L_101:	clrb	9,c
L_100:	swap.a	c,d1
	add.a	d,c			; Add one row up or down
	swap.a	c,d1
L_30:
	brbc	14,a,L_32		; No col movement - continue
	brbc	10,c,L_102		; Col is not half-angle
	brbs	11,c,L_103		; Move and clear toggle
	setb	11,c			; Move col next time around
	brcc	L_32			; Iterate
L_103:	clrb	11,c			; Don't move col next time around
L_102:	brbc	15,a,L_33		; Moving left
	add.s	b,b			; Moving right - shift mask left
	brnz.s	b,L_32			; Still within nibble - continue
	add.a	1,d1			; Outside - next nibble
	inc.s	b			; Set mask to 1
	brnz.s	b,L_32			; Continue
L_33:
	srb.s	b			; Moving left - shift mask right
	brnz.s	b,L_32			; Still within nibble - continue
	inc.s	b			; Set mask to 1
	add.s	b,b			; ...2
	add.s	b,b			; ...4
	add.s	b,b			; ...8
	sub.a	1,d1			; And move to prev nibble
L_32:
	dec.x	b			; Decrement pixel counter
	brz.x	b,L_35
	jump.3	sprite_draw		; Continue drawing

; Restore/update global registers and return
L_35:
	move.1	3,p			; Zero extend coll ctr to 5 nibbles
	clr.p	a
	inc.1	p
	clr.p	a
	move.w	r3,c
	rrn.w	c
	rrn.w	c
	rrn.w	c
	rrn.w	c
	clr.a	c
	or.a	a,c
	rln.w	c
	rln.w	c
	rln.w	c
	rln.w	c
	move.w	c,r3			; R3.4:8 = collision counter
	move.1	0,p
	ret


; Swap registers with their saved contents.
; Wrecks C.a.

TOH=#705b0
TOS=#70579
FP=#70574
Free_size=#7066e

swap_regs:
	move.a	a,c
	push.a	c

	swap.a	c,d0
	move.5	TOH,d0
	move.a	@d0,a
	move.a	c,@d0

	move.5	TOS,d0
	move.a	@d0,c
	swap.a	c,d1
	move.a	c,@d0

	move.5	FP,d0
	move.a	@d0,c
	swap.a	c,b
	move.a	c,@d0

	move.5	Free_size,d0
	move.a	@d0,c
	swap.a	d,c
	move.a	c,@d0

	move.a	a,d0
	pop.a	c
	move.a	c,a
	ret


; Jump table for first nibble

sprite_exect:
sprite_exec_table=sprite_exect-reloc

	data.a	spr_x_nop-reloc
	data.a	spr_x_grop-reloc
	data.a	spr_x_ena_coll-reloc
	data.a	spr_x_dis_coll-reloc
	data.a	spr_x_move-reloc
	data.a	spr_x_nop-reloc
	data.a	spr_x_nop-reloc
	data.a	spr_x_draw1-reloc
	data.a	spr_x_drawn-reloc
	data.a	spr_x_push-reloc
	data.a	spr_x_nop-reloc
	data.a	spr_x_setpix-reloc
	data.a	spr_x_end-reloc
	data.a	spr_x_drawnn-reloc
	data.a	spr_x_movenn-reloc
	data.a	spr_x_nop-reloc

; Jump table for second nibble of 9x group (PUSH/POP)

sprite_pusht:
push_table=sprite_pusht-reloc

	data.a	spr_x_push_coll-reloc
	data.a	spr_x_push_pt-reloc
	data.a	spr_x_pop_xy-reloc
	data.a	spr_x_pop_rot-reloc
	data.a	spr_x_nop-reloc
	data.a	spr_x_nop-reloc
	data.a	spr_x_nop-reloc
	data.a	spr_x_nop-reloc
	data.a	spr_x_nop-reloc
	data.a	spr_x_nop-reloc
	data.a	spr_x_nop-reloc
	data.a	spr_x_nop-reloc
	data.a	spr_x_nop-reloc
	data.a	spr_x_nop-reloc
	data.a	spr_x_nop-reloc


; Sprite angle-to-incrementation map.
; The angle entries are bit maps interpreted as follows:
;
;	0a0bcdef
;
; Where
;	a = Column is half-angle
;	b = Row is half-angle
;	c = Left/right
;	d = Column movement off/on
;	e = Up/down
;	f = Row movement off/on
;
sprite_anglet:
angle_table=sprite_anglet-reloc

	data.2	#0c			; 0	(0)
	data.2	#1d			; 1	(22.5)
	data.2	#0d			; 2	(45)
	data.2	#4d			; 3	(67.5)
	data.2	#01			; 4	(90)
	data.2	#45			; 5	(112.5)
	data.2	#05			; 6	(135)
	data.2	#15			; 7	(157.5)
	data.2	#04			; 8	(180)
	data.2	#17			; 9	(202.5)
	data.2	#07			; a	(225)
	data.2	#47			; b	(247.5)
	data.2	#03			; c	(270)
	data.2	#4f			; d	(292.5)
	data.2	#0f			; e	(315)
	data.2	#1f			; f	(337.5)


; Various variables

grob_basev:
grob_base=grob_basev-reloc
	data.a	0			; GROB base address

end: