
	include "nesdefs.dasm"

;;;;; ZERO-PAGE VARIABLES

	seg.u ZEROPAGE
	org $0
        
Ptr1		.word	; for temporary pointer storage

TrackFrac	.byte	; fractional position along track
Speed		.byte	; speed of car
TimeOfDay	.word	; 16-bit time of day counter
; Variables for preprocessing step
XPos		.word	; 16-bit X position
XVel		.word	; 16-bit X velocity
TPos		.word	; 16-bit track position
TrackLookahead	.byte	; current fractional track increment
; Variables for track generation
Random		.byte	; random counter
GenTarget	.byte	; target of current curve
GenDelta	.byte	; curve increment 
GenCur		.byte	; current curve value

ZOfs		.byte	; counter to draw striped center line
Weather		.byte	; bitmask for weather
Heading		.word	; sky scroll pos.

NumRoadScanlines = 112
NumRoadSegments = 56
; Preprocessing result: X positions for all track segments 
RoadX0		ds NumRoadSegments

; Generated track curve data
TrackLen	equ 5
TrackData	ds TrackLen

InitialSpeed	equ 10	; starting speed

;;;;; OTHER VARIABLES

	seg.u RAM
	org $300

;;;;; NES CARTRIDGE HEADER

	NES_HEADER 0,2,1,1 ; mapper 0, 2 PRGs, 1 CHR, vert mirror

;;;;; START OF CODE

Start:
	NES_INIT	; set up stack pointer, turn off PPU
        jsr WaitSync	; wait for VSYNC
        jsr ClearRAM	; clear RAM
        jsr WaitSync	; wait for VSYNC (and PPU warmup)

        jsr FillVRAM	; set PPU video RAM

        jsr RoadSetup

	lda #0
        sta PPU_ADDR
        sta PPU_ADDR	; PPU addr = $0000
        sta PPU_SCROLL
        sta PPU_SCROLL  ; scroll = $0000
        lda #MASK_BG|MASK_SPR|MASK_BG_CLIP
        sta PPU_MASK 	; enable rendering (first!)
        lda #CTRL_NMI
        sta PPU_CTRL	; enable NMI
        
.endless
	jmp .endless	; endless loop

;;;;; ROAD SUBS

RoadSetup: subroutine
        lda #1
        sta Random
        lda #InitialSpeed
        sta Speed
        lda #0
        sta TimeOfDay+1
        lda #0
        sta Weather
	rts

RoadPreSetup: subroutine
; Set up some values for road curve computation,
; since we have some scanline left over.
	lda #0
        sta XVel
        sta XVel+1
        sta XPos
        sta XPos+1
        lda TrackFrac
        sta TPos
        lda #0
        sta TPos+1
        lda #10		; initial lookahead
        sta TrackLookahead
	rts

RoadPostFrame: subroutine
; Advance position on track
; TrackFrac += Speed
	lda TrackFrac
        clc
        adc Speed
        sta TrackFrac
        bcc .NoGenTrack ; addition overflowed?
        jsr GenTrack	; yes, generate new track segment
.NoGenTrack
; TimeOfDay += 1
        inc TimeOfDay
        bne .NoTODInc
        inc TimeOfDay+1
        lda TimeOfDay+1
; See if it's nighttime yet, and if the stars come out
        clc
        adc #8
        and #$3f
        cmp #$35
        ror
        sta Weather
.NoTODInc
	lda Heading
        sec
        sbc XPos+1
        sta Heading
        bit XPos+1
        bmi .NegHeading
        lda Heading+1
        sbc #0
        sta Heading+1
        rts
.NegHeading
        lda Heading+1
        sbc #$ff
        sta Heading+1
	rts

; Compute road curve from bottom of screen to horizon.
PreprocessCurve subroutine
	ldx #NumRoadSegments-1
.CurveLoop
; Modify X position
; XPos += XVel (16 bit add)
	lda XPos
        clc
        adc XVel
        sta XPos
        lda XPos+1
        adc XVel+1
        sta XPos+1
        sta RoadX0,x	; store in RoadX0 array
; Modify X velocity (slope)
; XVel += TrackData[TPos]
        ldy TPos+1
        lda TrackData,y
        clc		; clear carry for ADC
        bmi .CurveLeft	; track slope negative?
        adc XVel
        sta XVel
        lda XVel+1
        adc #0		; carry +1
        jmp .NoCurveLeft
.CurveLeft
        adc XVel
        sta XVel
        lda XVel+1
        sbc #0		; carry -1
        nop ; make the branch timings are the same
.NoCurveLeft
        sta XVel+1
; Advance TPos (TrackData index)
; TPos += TrackLookahead
	lda TPos
        clc
        adc TrackLookahead
        sta TPos
        lda TPos+1
        adc #0
        sta TPos+1
; Go to next segment
	inc TrackLookahead ; see further along track
        dex
        bpl .CurveLoop
        rts

; Generate next track byte
GenTrack subroutine
; Shift the existing track data one byte up
; (a[i] = a[i+1])
	ldx #0
.ShiftTrackLoop
	lda TrackData+1,x
        sta TrackData,x
        inx
        cpx #TrackLen-1
        bne .ShiftTrackLoop
; Modify our current track value and
; see if it intersects the target value
	lda GenCur
        clc
        adc GenDelta
        cmp GenTarget
        beq .ChangeTarget   ; target == cur?
        bit GenTarget	    ; we need the sign flag 
        bmi .TargetNeg	    ; target<0?
        bcs .ChangeTarget   ; target>=0 && cur>=target?
        bcc .NoChangeTarget ; branch always taken
.TargetNeg
        bcs .NoChangeTarget ; target<0 && cur<target?
; Generate a new target value and increment value,
; and make sure the increment value is positive if
; the target is above the current value, and negative
; otherwise
.ChangeTarget
	lda Random
	jsr NextRandom	; get a random value
	sta Random
        and #$3f	; range 0..63
        sec
        sbc #$1f	; range -31..32
        sta GenTarget	; -> target
        cmp GenCur
        bmi .TargetBelow ; current > target?
	lda Random
        jsr NextRandom	; get a random value
	sta Random
        and #$f		; mask to 0..15
        jmp .TargetAbove
.TargetBelow
	lda Random
	jsr NextRandom
	sta Random
        ora #$f0	; mask to -16..0
.TargetAbove
        ora #1		; to avoid 0 values
        sta GenDelta	; -> delta
        lda GenCur
.NoChangeTarget
; Store the value in GenCur, and also
; at the end of the TrackData array
	sta GenCur
	sta TrackData+TrackLen-1
	rts


; fill video RAM
FillVRAM: subroutine
	PPU_SETADDR $2000
        lda #<RoadTables
        sta Ptr1
        lda #>RoadTables
        sta Ptr1+1
	ldx #8
        ldy #0
.loop:
	lda (Ptr1),y
	sta PPU_DATA
        iny
	bne .loop
        inc Ptr1+1
	dex
	bne .loop
        rts

; set palette colors
SetPalette: subroutine
	PPU_SETADDR $3f00
        ldx #16
.loop:
	lda Palette,y
	sta PPU_DATA
        iny
        dex
	bne .loop
        rts

; set sprite 0 (A = top scanline)
SetSprite0: subroutine
	sta $200	;ypos
        lda #$00	;code
        sta $201
        lda #$20	;flags
        sta $202
        lda #$fe	;xpos
        sta $203
	rts

;;;;; COMMON SUBROUTINES

	include "nesppu.dasm"

;;;;; INTERRUPT HANDLERS

            MAC SLEEP            ;usage: SLEEP n (n>1)
.CYCLES     SET {1}

                IF .CYCLES < 2
                    ECHO "MACRO ERROR: 'SLEEP': Duration must be > 1"
                    ERR
                ENDIF

                IF .CYCLES & 1
                    bit $00
.CYCLES             SET .CYCLES - 3
                ENDIF
            
                REPEAT .CYCLES / 2
                    nop
                REPEND
            ENDM

;;;
;;; NMI HANDLER
;;;

NMIHandler: subroutine
; save registers
	SAVE_REGS
; cycle palette colors
	lda TPos		; track position
        lsr			; / 2
        and #16			; now either 0 or 16
        tay			; Y is palette offset
	jsr SetPalette		; call SetPalette
        PPU_SETADDR $2000	; reset PPU address
; load sprite zero
        lda #109
        jsr SetSprite0		; set sprite 0 position
	lda #$02
        sta PPU_OAM_DMA		; send sprites to PPU
; setup sky scroll
	lda Heading+1		; heading scroll pos.
        sta PPU_SCROLL		; X scroll
        lda #0
        sta PPU_SCROLL		; Y scroll = 0
; do road calculations
        jsr RoadPreSetup
        jsr PreprocessCurve
        jsr RoadPostFrame
; wait for sprite 0
.wait0	bit PPU_STATUS
        bvs .wait0	; until bit 6 set
.wait1	bit PPU_STATUS
        bvc .wait1	; until bit 6 clear
; alter horiz. scroll position for each scanline
        ldy #0
.loop
; pad out rest of scanline
        SLEEP 80
; change scroll register
	tya		; Y -> A
        lsr		; A / 2
        tax		; A -> X
	lda RoadX0,x	; get X offset of scanline
        eor #$80	; + 128
        sta PPU_SCROLL	; horiz byte
        sta PPU_SCROLL	; vert byte (ignored)
; take back 1 cycle every 4th line
	tya		; Y -> A
        and #3		; mask first 2 bits
        bne .skipcyc	; -1 cycle every 4 lines
.skipcyc
; next loop iteration
        iny
        cpy #NumRoadScanlines
        bne .loop	; do next scanline
; restore registers and return from interrupt
        RESTORE_REGS
	rti

;;;;; CONSTANT DATA

;;{pal:"nes",layout:"nes"};;
Palette:
	hex 0F3F06300F19063F0F1916063F180801
	hex 0F30163F0F19163F0F1906160F180801
;;
RoadTables:
	incbin "road/nametable.dat"
	incbin "road/attribute.dat"
	incbin "road/nametable1.dat"
	incbin "road/attribute1.dat"

;;;;; CPU VECTORS

	NES_VECTORS

;;;;; TILE SETS

	org $10000
	incbin "road/road.chr"
