[stella] Pattern Processor code help

Subject: [stella] Pattern Processor code help
From: Wade Brown <wbrown@xxxxxxxxxxxxx>
Date: Fri, 09 May 2003 05:13:57 -0400
Hello,
I stopped working on this a few months ago and just recently picked it up again - I'm a relative newbie to 6502 and 2600 coding so please, try to bear with me...


The idea for the game is a Satan's hollow clone - the attached source merely draws one alien which is supposed to spin in a circle.

I've tried to write a "pattern processor" - encoding x,y motions and number of frames (comments in code explain the format) - however there is a bug that I cannot solve which causes the alien to drift slowly downward (he should turn in stationary circles).

Can anyone help?

Thanks,

Wade

;-----------------------------------------------
; test-missile.asm
;
;  compile with Dasm 2.12.4
;
; Wade Brown 2001, 2002
;-----------------------------------------------
	processor 6502
	include vcs.h
;-----------------------------------------------
; Equates                          
; Note : 76 cycles/scanline
;		 228 color clocks/scanline
;		 Each Line - 68 pixels of H. Blank followed by 160 Visible pixels
;		 vblank = 37 scanlines
;		 overscan = 30 scanlines 
;-----------------------------------------------
T64_VBLANK		EQU	43	; 43 * 64 = 2752 cycles	(vblank= (76 * 37 = 2812 cycles))
T64_OSCAN		EQU	35	; 35 * 64 = 2240 cycles (oscan = (76 * 30 = 2280 cycles))
;------------------------------------------------
; ROM definitions
;------------------------------------------------
RomStart        equ     $F000
RomEnd          equ     $FFFF
IntVectors      equ     $FFFA
;------------------------------------------------
; Screen X,Y Boundaries 
;  Y ranges from 192 - 1
;  X ranges from 0 to 159
;------------------------------------------------
Y_MAX		EQU	192
Y_MIN		EQU	8
X_MAX		EQU 150
X_MIN		EQU 0
TOP_LINE	EQU 192
;------------------------------------------------
; Object Boundaries - used in ROM InitialPos table
;------------------------------------------------
SHIP_X_MIN	EQU	0
ENEMY_X_MIN	EQU	0
SHIP_Y_MIN	EQU 40
ENEMY_Y_MIN	EQU 40

SHIP_X_MAX EQU 153
ENEMY_X_MAX EQU 153
SHIP_Y_MAX	EQU 24
ENEMY_Y_MAX EQU 192
;
ENEMY_HEIGHT EQU 16
;------------------------------------------------
; Object Colors (?)
;------------------------------------------------
BG_COLOR		EQU $A5		; blue from 6 to A
ENEMY_COLOR		EQU $D0		; Green from B, D
SHIP_COLOR		EQU $32		; red from 20, 40
;-----------------------------------------------
; Macros
;		(<#PatDocileBeg - <#EnemyPatTable)-1		

;-----------------------------------------------
	mac	WAIT_TIMER	
.1		LDA	INTIM	;Wait for the proper scan line
		BNE	.1
	endm

	mac Test
		#00
	endm 

;-----------------------------------------------
; Ram Segment (128 bytes)
;-----------------------------------------------
    SEG.U vars
    org $80
;-----------------------------------------------
; Game Variables
;-----------------------------------------------
GameVars				; Set X register to this address and jmp to WarmStart 
EnemyX	DS 1			; p0
ShipX	DS	1				; p1
EnemyY	DS 1
ShipY	DS	1
;
; pattern processor variables
;		
PatFrame DS 1
PatIndex DS 1
NextIndex DS 1
;
; multi-sprite variables
;
EnemySConf DS 1
MxPos DS 1
;-----------------------------------------------
FrameCount DS 1
Temp	DS  1
Temp1	DS  1
;-----------------------------------------------
;  Code Start
;-----------------------------------------------
	;----------------------------------------------
	; <BoilerPlate>
	;----------------------------------------------
	SEG code
	org RomStart
	;-----------------------------------------------
	; Reset console RAM, TIA registers
	;-----------------------------------------------
ResetEntry
	SEI
	CLD
	LDX #$FF
	TXS
WarmStart		; set X to start location of memory to clear (i.e GameVars)
	LDA	#0
.Clear
	STA	$00,X	; clears memory from location x to location 1 (Does not clear VSYNC at
				; address 0)
	DEX 
	BNE	.Clear
	;----------------------------------------------
	; </BoilerPlate>
	;----------------------------------------------
	JSR GameInit	
	;-----------------------------------------------
	; Main Loop Start 
	;-----------------------------------------------
MainLoop        
	JSR VerticalSync    ; 
    JSR VerticalBlank   ; 
    JSR MainScreen      ; MainScreen does Overscan
    JMP MainLoop        ; Repeat
;***********************************************
; Subs
;***********************************************

;-----------------------------------------------
; VSYNC Handler
;-----------------------------------------------
	;----------------------------------------------
	; <BoilerPlate>
	;----------------------------------------------
VerticalSync	SUBROUTINE	
	LDA #$02            ;
    STA WSYNC           ; Finish current line
    STA VSYNC           ; start vertical sync
    LDA #$03            ;
    STA TIM64T          ;
    JMP WaitIntimReady  ; Wait until vertical sync finished
	;----------------------------------------------
	; </BoilerPlate>
	;----------------------------------------------
;-----------------------------------------------
; Vblank handler
;-----------------------------------------------
	;----------------------------------------------
	; <BoilerPlate>
	;----------------------------------------------
VerticalBlank   SUBROUTINE
		; A =0 
	STA WSYNC           ; Finish current line
    STA VSYNC           ; Stop vertical sync
    LDA #$02            ;
    STA VBLANK          ; Start vertical blank
    LDA #T64_VBLANK       ; 43 * 64 = 2752 cycles	(vblank= (76 * 37 = 2812 cycles))
    STA TIM64T          ; Init timer
	;----------------------------------------------
	; </BoilerPlate>
	;----------------------------------------------
	LDA FrameCount
	JSR SetNewState
	LDY #00
	JSR ProcPattern
	;----------------------------------------------
	; Position p0
	;----------------------------------------------
	LDY #0
	LDX EnemyX
	JSR PosPlayer
	;----------------------------------------------
	; <BoilerPlate>
	;----------------------------------------------
WaitIntimReady  
	LDA INTIM           ; Wait until vertical blank finished
    BNE WaitIntimReady  ;
    RTS
	;----------------------------------------------
	; </BoilerPlate>
	;----------------------------------------------
;-----------------------------------------------
; Main Screen
;-----------------------------------------------
	;----------------------------------------------
	; <BoilerPlate>
	;----------------------------------------------
MainScreen	SUBROUTINE      
	STA WSYNC           ; Finish current line
	STA VBLANK          ; Stop vertical blank
	LDY #192			; 192 Scanlines
	;----------------------------------------------
	; </BoilerPlate>
	;----------------------------------------------
	INC FrameCount
	; Set Background Color
	LDA #BG_COLOR
	STA COLUBK
	;
	LDA #ENEMY_COLOR
	STA COLUP0
nextLine        
	STA WSYNC
    TYA
	SBC EnemyY			; a = Line - EnemyY
	ADC #ENEMY_HEIGHT	; a = a + 
	BCC SkipDraw		;
	TAX				  ; get the grIndex in X 		
	LDA GrEscort,X
	.byte $2c
SkipDraw     
	LDA #$00   
	STA  GRP0
	;----------------------------------------------
	; <BoilerPlate>
	;----------------------------------------------
	DEY
	BNE nextLine
	; Screen Finished, disable video and set the oscan timer
	LDA #2
	STA WSYNC			;Finish this scanline.
	STA VBLANK			; video OFF (important)
	LDA	#T64_OSCAN		; total number of cycles for 30 scan-line OverScan period
	STA	TIM64T			;Set the timer for start of screen
	;----------------------------------------------
	; </BoilerPlate>
	;----------------------------------------------
	;-----------------------------------------------
	; overscan handler 
	;-----------------------------------------------
	JSR joy				; get joystick input	
	;----------------------------------------------
	; <BoilerPlate>
	;----------------------------------------------
	WAIT_TIMER
	RTS
	;----------------------------------------------
	; </BoilerPlate>
	;----------------------------------------------
;-----------------------------------------------
; GameInit
;-----------------------------------------------
GameInit	SUBROUTINE
	;-----------------------------------------------
	; Initial object positions, etcetera
	;-----------------------------------------------
	LDY #3
LoadPosTop
	LDA InitialPos,Y
	STA	EnemyX,Y
	DEY
	BPL LoadPosTop
	;INY				
	STY PatIndex		; init to $ff
	INY
	STY PatFrame
	RTS

;-----------------------------------------------
; joystick reading
;-----------------------------------------------
joy SUBROUTINE
	nop
	RTS
;-----------------------------------------------
; Player positionning
; x = xpos
; y = 0 for p0, 1 for p1
;-----------------------------------------------
PosPlayer	SUBROUTINE
    sta WSYNC           ;begin scanline
    lda HorzTable,X     ;+4  7  
    sta HMP0,Y           ;+3 10
    and #$0F            ;+2 12
    tax                 ;+2 14
P0      
	dex                 ;+2 16
    bpl P0              ;when branch not taken: +2 (18 + x*5)
    sta RESP0,Y         ;(21 + x*5)
    sta WSYNC
	sta HMOVE
    rts
;-----------------------------------------------
; Pattern Byte processing
;-----------------------------------------------
;-----------------------------------------------
; Process Pattern
;-----------------------------------------------
; PatByte Layout:
; d7-d6: frames
; d5-d3: x offset
; d0-d2: y offset
;  Note for x and y offset if high bit (d5 for x, d2 for y) set then it is negative offset
;  in 2's complement form....
;
ProcPattern
	JSR GetPatByte
	LDX #2
	JSR StorePatVal
; x offset
	TYA			; patbyte in A
	LSR
	LSR
	LSR			; shift into low order bits
	LDX #0		; for x values
	JSR StorePatVal	; 
	RTS
;	
; OUT: A, Y set to pat byte contents
GetPatByte
	; 
	LDX PatIndex
NextByte	
	LDY PatFrame
	BNE NextFrame
	; if (PatFrame == 0) {
	; 	get next pattern byte
	INX	
	STX PatIndex	; inc pat index
	; get patbyte
	LDA EnemyPatTable,X
	BEQ ResetPattern	; jump if sentinel end of pattern byte (0)
	; 	get/set new PatFrame 
	AND #$C0
	CLC
	ROL 
	ROL
	ROL
	STA PatFrame		
NextFrame
	DEC PatFrame
	LDA EnemyPatTable,X
	BEQ ResetPattern
	TAY
	RTS
ResetPattern
	TAX
	DEX
	STX PatIndex
	STA PatFrame
	BCS NextByte	; ALWAYS Taken
;-----------------------------------------------
; StorePatVal
; A = patbyte
; x = 0 for x, 2 for y 
;-----------------------------------------------
StorePatVal
	STX Temp1	; save X 
	AND #$07	; mask all but lower three bits (y)
	BIT NegMask	; 
	BEQ NoNeg	; neg bit set
	ORA #$F8	; flip top 5 bits to 1
NoNeg			; positive y offset
	LDX Temp1	; restore X
	STA Temp	; motion offset 
	LDA EnemyX,X	; current pos
	CLC
	ADC Temp	; calc new value
	STA EnemyX,X	; store new pos
	RTS		; 
;
; In: randomized value in A
;	   gets new PatIndex in 

SetNewState	SUBROUTINE
;	AND #$07
;	TAX
;	LDA PatIndexTable,X
;	STA NextIndex
	RTS
;-----------------------------------------------
; Graphics Start
;-----------------------------------------------
GrPage ALIGN 256
;
; Alien "Escort" (16 bytes)
;
GrEscort		
	.byte	%10000001		; X......X
	.byte	%10000001		; X......X
	.byte	%01011010		; .X.XX.X.
	.byte	%00111100		; ..XXXX..
	.byte	%10111101		; X.XXXX.X
	.byte	%01100110		; .XX..XX.
	.byte	%01111110		; .XXXXXX.
	.byte	%11111111		; XXXXXXXX
	.byte	%11111111		; XXXXXXXX
	.byte	%10111101		; X.XXXX.X
	.byte	%11111111		; XXXXXXXX
	.byte	%11111111		; XXXXXXXX
	.byte	%01111110		; .XXXXXX.
	.byte	%00111100		; ..XXXX..
	.byte	%01000010		; .X....X.
	.byte	%10000001		; X......X
;
;
;
GrEscort1		
	.byte	%01000010		; .X....X.
	.byte	%10000001		; X......X
	.byte	%01011010		; .X.XX.X.
	.byte	%00111100		; ..XXXX..
	.byte	%00111100		; ..XXXX..
	.byte	%11100111		; XXX..XXX
	.byte	%01100110		; .XX..XX.
	.byte	%11111111		; XXXXXXXX
	.byte	%11111111		; XXXXXXXX
	.byte	%10111101		; X.XXXX.X
	.byte	%11111111		; XXXXXXXX
	.byte	%11111111		; XXXXXXXX
	.byte	%01111110		; .XXXXXX.
	.byte	%00111100		; ..XXXX..
	.byte	%01000010		; .X....X.
	.byte	%01000010		; .X....X.
;
; Player (12 bytes)
;
GrShip
	.byte	%10011001		; 1..11..1
	.byte	%11100111		; 111..111
	.byte	%11000011		; 11....11
	.byte	%11000011		; 11....11
	.byte	%11000011		; 11....11
	.byte	%11000011		; 11....11
	.byte	%11000011		; 11....11
	.byte	%11000011		; 11....11
	.byte	%11100111		; 111..111
	.byte	%10011001		; 1..11..1
	.byte	%00011000		; ...11...
	.byte	%00011000		; ...11...
;
; 256 bytes for variables
;
VarsPage	ALIGN 256
;------------------------------------------------------------------------
; Horz Motion Values: works from 0 to screen edge
;  From Manuel Polik's Gunfight
;------------------------------------------------------------------------
HorzTable    ;this must not cross a page boundary
    .byte $30,$20,$10,$00,$F0,$E0,$D0,$C0,$B0,$A0,$90
    .byte $71,$61,$51,$41,$31,$21,$11,$01,$F1,$E1,$D1,$C1,$B1,$A1,$91
    .byte $72,$62,$52,$42,$32,$22,$12,$02,$F2,$E2,$D2,$C2,$B2,$A2,$92
    .byte $73,$63,$53,$43,$33,$23,$13,$03,$F3,$E3,$D3,$C3,$B3,$A3,$93
    .byte $74,$64,$54,$44,$34,$24,$14,$04,$F4,$E4,$D4,$C4,$B4,$A4,$94
    .byte $75,$65,$55,$45,$35,$25,$15,$05,$F5,$E5,$D5,$C5,$B5,$A5,$95
    .byte $76,$66,$56,$46,$36,$26,$16,$06,$F6,$E6,$D6,$C6,$B6,$A6,$96
    .byte $77,$67,$57,$47,$37,$27,$17,$07,$F7,$E7,$D7,$C7,$B7,$A7,$97
    .byte $78,$68,$58,$48,$38,$28,$18,$08,$F8,$E8,$D8,$C8,$B8,$A8,$98
    .byte $79,$69,$59,$49,$39,$29,$19,$09,$F9,$E9,$D9,$C9,$B9,$A9,$99
    .byte $7A,$6A,$5A,$4A,$3A,$2A,$1A,$0A,$FA,$EA,$DA,$CA,$BA,$AA,$9A
;
; Piero Cavina's multi-sprite tables....
newconftable	.byte $4,  $0,  $0,  $1
             	.byte $FF, $0,  $FF, $2
             	.byte $FF, $FF, $0,  $1

offsettable		.byte $0,  $10, $20, $10
             	.byte $FF, $0,  $FF, $0
             	.byte $FF, $FF, $0,  $0
;------------------------------------------------------------------------
;
; 
;------------------------------------------------------------------------
VarsPage1 Align 256
; Initial Object Positions
; EnemyX, ShipX, EnemyY,ShipY
InitialPos
	.byte #80, #80, #160, #SHIP_Y_MIN
;
; Docile, Warm
; Attack, Attack1
; Retreat, Retreat1
; Docile, Attack
PatIndexTable
	.BYTE #(<PatDocileBeg-<EnemyPatTable), #(<PatWarmBeg-<EnemyPatTable), #(<PatAttackBeg-<EnemyPatTable), #(<PatWarmBeg-<EnemyPatTable)
	.byte #(<PatAttackBeg-<EnemyPatTable), #(<PatAttack1Beg-<EnemyPatTable), #(<PatAttackBeg-<EnemyPatTable), #(<PatAttack1Beg-<EnemyPatTable)
;------------------------------------------------------------------------
;Object Colors
; Enemy, Ship
;------------------------------------------------------------------------
ObjColorTab
	.byte #ENEMY_COLOR, #ENEMY_COLOR, #ENEMY_COLOR, #ENEMY_COLOR, #ENEMY_COLOR, #ENEMY_COLOR, #ENEMY_COLOR, #ENEMY_COLOR
	.byte #SHIP_COLOR, #SHIP_COLOR, #SHIP_COLOR, #SHIP_COLOR, #SHIP_COLOR, #SHIP_COLOR, #SHIP_COLOR, #SHIP_COLOR
; PatByte Layout:
; d7-d6: frames
; d5-d3: x offset
; d0-d2: y offset
;  Note for x and y offset if high bit (d5 for x, d2 for y) set then it is negative offset
;  in 2's complement form....
;
EnemyPatTable
; $c4, $c4, $c8, $d8, $c1, $c2
; 111 (-1), 110 (-2), 101 (-3), 100 (-4)
PatDocileBeg		; Loose Circles
	;		4 x+,y0  4 x+, y0	4 x+,y-	  4 x+,y-	   4 x0,y-  4 x0,y-		 4 x-,y-	4 x-,y-
	.BYTE %11001000,%11001000,	%11001111,%11001111,  %11000111,%11000111,  %11111111, %11111111
	;		4 x-,y0	 4 x-,y0	 4 x-,y+	4 x-,y+		4 x0,y+	  4 x0,y+	 4 x+,y+	4 x+,y+
	.BYTE %11111000,%11111000,  %11111001,%11111001,  %11000001,%11000001,  %11001001, %11001001, $00
PatDocileEnd
;	

PatWarmBeg			; Tight Circles
	; %11001111, %11000111, %11111111
	;.BYTE %11111000, %11111001, %11000001, %11001001, $00
PatWarmEnd
;
PatAttackBeg
	.BYTE %11111110, %11000101, %11001100, $00
PatAttackEnd
;
PatAttack1Beg
	.BYTE %10111110, %10000101, %11011110, %11000100, %11011111, $00
PatAttack1End
;
PatRetreatBeg
	.BYTE %11001011, %11011010, %1110001, $00
PatRetreatEnd
;
PatRetreat1Beg
	.BYTE %11001011, %11001011, %01011010, %1110001, $00
PatRetreat1End
;
;
NegMask		HEX 04
DiffSwitchTbl	HEX	4080	;Left,Right difficulty flags
	ORG	IntVectors
NMI		.word	ResetEntry
Reset	.word	ResetEntry
Irq		.word	ResetEntry
Current Thread