c64-basic/infinite-road/infinite-road.asm

797 lines
14 KiB
NASM

!cpu 6510
; INFINITE ROAD
; Double-buffered pseudo-3D highway, custom character set and three-voice SID.
; Self-starting BASIC line: 10 SYS4096
* = $0801
!word basic_end
!word 10
!byte $9e
!text "4096"
!byte 0
basic_end: !word 0
* = $1000
GETIN = $ffe4
CHROUT = $ffd2
start:
jsr save_state
lda #0
sta $d020
sta $d021
jsr show_splash
jsr initialize_demo
lda #250
jsr wait_raster
main_loop:
jsr read_controls
lda quit_flag
beq keep_running
jmp exit_demo
keep_running:
lda paused
bne paused_frame
jsr update_scene
lda music_muted
bne skip_music
jsr update_music
skip_music:
jsr render_back_buffer
lda #250
jsr wait_raster
jsr flip_buffers
jmp main_loop
paused_frame:
lda #250
jsr wait_raster
jmp main_loop
save_state:
lda $d020
sta old_border
lda $d021
sta old_background
lda $d011
sta old_d011
lda $d016
sta old_d016
lda $d018
sta old_d018
lda $dd00
sta old_dd00
lda $d015
sta old_sprite_enable
lda $d010
sta old_sprite_msb
lda $d017
sta old_sprite_expand_y
lda $d01d
sta old_sprite_expand_x
lda $d01b
sta old_sprite_priority
lda $d000
sta old_sprite_x
lda $d001
sta old_sprite_y
lda $d027
sta old_sprite_color
lda $07f8
sta old_sprite_pointer
ldx #0
save_sid_loop:
lda $d400,x
sta old_sid,x
inx
cpx #25
bne save_sid_loop
rts
initialize_demo:
lda #0
sta $d020
sta $d021
sta curve_phase
sta road_phase
sta music_tick
sta music_step
sta paused
sta music_muted
sta quit_flag
sta front_buffer
lda #6
sta view_index
lda #2
sta road_speed
lda #0
sta palette
lda old_dd00
ora #3
sta $dd00
lda old_d011
and #$9f
ora #$10
sta $d011
lda old_d016
and #$e8
ora #8
sta $d016
lda #136
sta $d000
lda #76
sta $d001
lda $d010
and #$fe
sta $d010
lda $d017
ora #1
sta $d017
lda $d01d
ora #1
sta $d01d
lda $d01b
ora #1
sta $d01b
lda #$e0
sta $07f8
sta $0ff8
lda #1
sta $d015
lda #$1c
sta $d018
jsr prepare_buffers
jsr apply_palette
jsr initialize_sid
rts
prepare_buffers:
lda #$04
sta screen_base_hi
jsr clear_buffer
jsr draw_sky
lda #$0c
sta screen_base_hi
jsr clear_buffer
jsr draw_sky
lda #$0c
sta screen_base_hi
jsr render_road
lda #$04
sta screen_base_hi
jsr render_road
lda #$0c
sta screen_base_hi
rts
clear_buffer:
lda #0
sta $fb
lda screen_base_hi
sta $fc
ldx #3
ldy #0
clear_full_pages:
lda #0
sta ($fb),y
iny
bne clear_full_pages
inc $fc
dex
bne clear_full_pages
ldy #0
clear_tail:
lda #0
sta ($fb),y
iny
cpy #232
bne clear_tail
rts
draw_sky:
; Stars, a striped sun and a jagged horizon are identical in both buffers.
ldx #0
sky_star_loop:
lda star_rows,x
tay
lda row_lo,y
sta $fb
lda row_hi,y
clc
adc screen_base_hi
sta $fc
ldy star_columns,x
lda #5
sta ($fb),y
inx
cpx #22
bne sky_star_loop
ldy #9
lda row_lo,y
sta $fb
lda row_hi,y
clc
adc screen_base_hi
sta $fc
ldx #0
mountain_loop:
lda mountain_chars,x
ldy mountain_columns,x
sta ($fb),y
inx
cpx #40
bne mountain_loop
rts
render_back_buffer:
lda front_buffer
beq render_to_second
lda #$04
bne set_render_base
render_to_second:
lda #$0c
set_render_base:
sta screen_base_hi
jmp render_road
render_road:
ldx #0
road_row_loop:
stx row_index
txa
clc
adc #10
tay
lda row_lo,y
sta $fb
lda row_hi,y
clc
adc screen_base_hi
sta $fc
ldy #0
clear_road_row:
lda #0
sta ($fb),y
iny
cpy #40
bne clear_road_row
ldx row_index
lda curve_offsets,x
clc
adc curve_phase
and #63
tay
lda curve_centers,y
clc
adc view_index
sec
sbc #6
cmp #8
bcs center_not_low
lda #8
center_not_low:
cmp #32
bcc center_ready
lda #31
center_ready:
sta road_center
ldx row_index
sec
sbc road_half_width,x
bcs left_ready
lda #0
left_ready:
sta road_left
lda road_center
clc
adc road_half_width,x
cmp #40
bcc right_ready
lda #39
right_ready:
sta road_right
ldy road_left
lda #2
sta ($fb),y
iny
fill_road_loop:
cpy road_right
bcs place_right_edge
lda row_index
clc
adc road_phase
and #3
bne road_surface
tya
sec
sbc road_center
beq center_dash
road_surface:
lda row_index
and #1
clc
adc #6
bne store_road_char
center_dash:
lda #4
store_road_char:
sta ($fb),y
iny
bne fill_road_loop
place_right_edge:
lda #3
sta ($fb),y
ldx row_index
inx
cpx #15
beq road_render_done
jmp road_row_loop
road_render_done:
rts
update_scene:
lda curve_phase
clc
adc road_speed
and #63
sta curve_phase
lda road_phase
clc
adc road_speed
and #7
sta road_phase
rts
flip_buffers:
lda front_buffer
eor #1
sta front_buffer
beq show_first
lda #$3c
bne store_screen_select
show_first:
lda #$1c
store_screen_select:
sta $d018
rts
read_controls:
jsr GETIN
bne control_available
rts
control_available:
cmp #81
bne check_left
lda #1
sta quit_flag
rts
check_left:
cmp #65
bne check_right
lda view_index
beq controls_done
dec view_index
rts
check_right:
cmp #68
bne check_color
lda view_index
cmp #12
bcs controls_done
inc view_index
rts
check_color:
cmp #67
bne check_mute
inc palette
lda palette
cmp #3
bcc apply_new_palette
lda #0
sta palette
apply_new_palette:
jsr apply_palette
rts
check_mute:
cmp #77
bne check_pause
lda music_muted
eor #1
sta music_muted
bne mute_now
jsr initialize_sid
rts
mute_now:
jsr silence_sid
rts
check_pause:
cmp #32
bne check_speed_one
lda paused
eor #1
sta paused
bne pause_now
lda music_muted
bne controls_done
jsr initialize_sid
rts
pause_now:
jsr silence_sid
rts
check_speed_one:
cmp #49
bne check_speed_two
lda #1
sta road_speed
rts
check_speed_two:
cmp #50
bne check_speed_three
lda #2
sta road_speed
rts
check_speed_three:
cmp #51
bne controls_done
lda #3
sta road_speed
controls_done:
rts
apply_palette:
; Blank the display while color RAM is refreshed, avoiding a partial frame.
lda $d011
sta display_d011
and #$ef
sta $d011
lda palette
asl
asl
tax
lda palette_colors,x
sta sky_color
inx
lda palette_colors,x
sta horizon_color
inx
lda palette_colors,x
sta road_color
inx
lda palette_colors,x
sta sun_color
ldx #0
color_row_loop:
stx row_index
cpx #8
bcc use_sky_color
cpx #10
bcc use_horizon_color
lda road_color
bne color_selected
use_horizon_color:
lda horizon_color
bne color_selected
use_sky_color:
lda sky_color
color_selected:
sta row_color
lda color_row_lo,x
sta $fb
lda color_row_hi,x
sta $fc
ldy #0
color_columns:
lda row_color
sta ($fb),y
iny
cpy #40
bne color_columns
ldx row_index
inx
cpx #25
bne color_row_loop
lda sun_color
sta $d027
lda display_d011
sta $d011
rts
initialize_sid:
jsr silence_sid
lda #$08
sta $d402
lda #$08
sta $d403
lda #$09
sta $d405
lda #$88
sta $d406
lda #$04
sta $d40c
lda #$a6
sta $d40d
lda #$02
sta $d413
lda #$84
sta $d414
lda #$0f
sta $d418
rts
update_music:
inc music_tick
lda music_tick
cmp #5
bcc music_done
lda #0
sta music_tick
ldx music_step
lda #0
sta $d404
sta $d40b
sta $d412
lda lead_sequence,x
tay
lda note_lo,y
sta $d400
lda note_hi,y
sta $d401
lda #$41
sta $d404
lda bass_sequence,x
tay
lda note_hi,y
lsr
sta $d408
lda note_lo,y
ror
sta $d407
lda #$11
sta $d40b
lda drum_sequence,x
beq no_drum
lda #$26
sta $d40e
lda #$19
sta $d40f
lda #$81
sta $d412
no_drum:
inc music_step
lda music_step
and #15
sta music_step
music_done:
rts
silence_sid:
lda #0
sta $d404
sta $d40b
sta $d412
sta $d418
rts
wait_raster:
wait_raster_enter:
cmp $d012
bne wait_raster_enter
wait_raster_leave:
cmp $d012
beq wait_raster_leave
rts
show_splash:
lda #147
jsr CHROUT
lda #<splash_text
sta $fb
lda #>splash_text
sta $fc
print_splash:
ldy #0
lda ($fb),y
beq wait_for_space
jsr CHROUT
inc $fb
bne print_splash
inc $fc
jmp print_splash
wait_for_space:
jsr GETIN
cmp #32
bne wait_for_space
rts
exit_demo:
jsr silence_sid
ldx #0
restore_sid_loop:
lda old_sid,x
sta $d400,x
inx
cpx #25
bne restore_sid_loop
lda old_sprite_enable
sta $d015
lda old_sprite_msb
sta $d010
lda old_sprite_expand_y
sta $d017
lda old_sprite_expand_x
sta $d01d
lda old_sprite_priority
sta $d01b
lda old_sprite_x
sta $d000
lda old_sprite_y
sta $d001
lda old_sprite_color
sta $d027
lda old_sprite_pointer
sta $07f8
lda old_dd00
sta $dd00
lda old_d018
sta $d018
lda old_d016
sta $d016
lda old_d011
sta $d011
lda old_border
sta $d020
lda old_background
sta $d021
lda #147
jsr CHROUT
rts
splash_text:
!byte 13,13,13,154
!text " INFINITE ROAD"
!byte 13,13,5
!text " AN ENDLESS SYNTHWAVE NIGHT DRIVE"
!byte 13,13,13,153
!text " CONTROLS"
!byte 13,13,158
!text " A/D"
!byte 5
!text " VIEW "
!byte 158
!text "1-3"
!byte 5
!text " SPEED "
!byte 158
!text "C"
!byte 5
!text " COLOR"
!byte 13
!text " "
!byte 158
!text "M"
!byte 5
!text " MUSIC "
!byte 158
!text "SPACE"
!byte 5
!text " PAUSE "
!byte 158
!text "Q"
!byte 5
!text " QUIT"
!byte 13,13,13,5
!text " HTTPS://C64.P20.DEV"
!byte 13,13,152
!text " RELEASED 21 OCTOBER 2026"
!byte 13,13,13,158
!text " PRESS SPACE TO START"
!byte 0
; Perspective and scene tables.
road_half_width: !byte 2,3,4,5,6,7,8,9,10,11,13,15,17,18,19
curve_offsets: !byte 0,1,2,3,5,7,9,12,15,18,21,24,27,30,33
curve_centers:
!byte 20,20,20,21,21,21,22,22,22,23,23,23,23,23,23,23
!byte 23,23,23,23,23,23,23,22,22,22,21,21,21,20,20,20
!byte 20,20,20,19,19,19,18,18,18,17,17,17,17,17,17,17
!byte 17,17,17,17,17,17,17,18,18,18,19,19,19,20,20,20
star_rows: !byte 1,1,2,2,3,3,4,4,5,5,6,6,7,7,2,3,5,6,1,4,6,7
star_columns: !byte 3,34,8,28,16,37,5,31,12,25,2,35,18,38,21,1,33,9,24,19,29,14
mountain_columns:
!byte 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19
!byte 20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39
mountain_chars:
!byte 19,19,18,19,18,18,19,19,18,19,19,18,18,18,19,19,18,19,18,19
!byte 19,18,19,19,18,18,19,18,19,19,19,18,18,19,19,18,19,18,19,19
palette_colors: !byte 3,4,11,7,14,6,12,1,10,2,9,8
note_lo: !byte $c4,$22,$81,$e1,$4b,$b8,$2a,$a1
note_hi: !byte $10,$13,$15,$18,$1b,$1e,$22,$26
lead_sequence: !byte 0,2,4,5,4,2,7,5,0,2,4,6,5,4,2,1
bass_sequence: !byte 0,0,0,0,3,3,3,3,5,5,5,5,3,3,1,1
drum_sequence: !byte 1,0,0,0,1,0,1,0,1,0,0,0,1,0,1,0
row_lo:
!for row,0,24 { !byte <(row*40) }
row_hi:
!for row,0,24 { !byte >(row*40) }
color_row_lo:
!for row,0,24 { !byte <($d800+row*40) }
color_row_hi:
!for row,0,24 { !byte >($d800+row*40) }
screen_base_hi: !byte 0
front_buffer: !byte 0
curve_phase: !byte 0
road_phase: !byte 0
road_speed: !byte 0
view_index: !byte 0
palette: !byte 0
paused: !byte 0
music_muted: !byte 0
quit_flag: !byte 0
music_tick: !byte 0
music_step: !byte 0
row_index: !byte 0
road_center: !byte 0
road_left: !byte 0
road_right: !byte 0
sky_color: !byte 0
horizon_color: !byte 0
road_color: !byte 0
sun_color: !byte 0
row_color: !byte 0
display_d011: !byte 0
old_border: !byte 0
old_background: !byte 0
old_d011: !byte 0
old_d016: !byte 0
old_d018: !byte 0
old_dd00: !byte 0
old_sprite_enable: !byte 0
old_sprite_msb: !byte 0
old_sprite_expand_y: !byte 0
old_sprite_expand_x: !byte 0
old_sprite_priority: !byte 0
old_sprite_x: !byte 0
old_sprite_y: !byte 0
old_sprite_color: !byte 0
old_sprite_pointer: !byte 0
old_sid: !fill 25,0
; Custom 8x8 character set at $3000. Only the scene glyphs are populated.
* = $3000
!fill 8,0 ; 0 blank
!byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff ; 1 solid
!byte $80,$c0,$e0,$f0,$f8,$fc,$fe,$ff ; 2 left road edge
!byte $01,$03,$07,$0f,$1f,$3f,$7f,$ff ; 3 right road edge
!byte $00,$00,$18,$18,$18,$18,$00,$00 ; 4 lane marker
!byte $00,$10,$54,$38,$fe,$38,$54,$10 ; 5 star
!byte $88,$22,$88,$22,$88,$22,$88,$22 ; 6 road texture A
!byte $22,$88,$22,$88,$22,$88,$22,$88 ; 7 road texture B
!byte $3c,$7e,$ff,$ff,$ff,$ff,$7e,$3c ; 8 sun solid
!byte $3c,$7e,$ff,$00,$00,$ff,$7e,$3c ; 9 sun stripe
!byte $00,$00,$01,$03,$07,$0f,$1f,$3f ; 10 sun top left
!byte $00,$00,$ff,$ff,$ff,$ff,$ff,$ff ; 11 sun top middle
!byte $00,$00,$80,$c0,$e0,$f0,$f8,$fc ; 12 sun top right
!byte $3f,$7f,$ff,$ff,$ff,$ff,$7f,$3f ; 13 sun left
!byte $fc,$fe,$ff,$ff,$ff,$ff,$fe,$fc ; 14 sun right
!byte $3f,$1f,$0f,$07,$03,$01,$00,$00 ; 15 sun bottom left
!byte $ff,$ff,$ff,$ff,$ff,$ff,$00,$00 ; 16 sun bottom middle
!byte $fc,$f8,$f0,$e0,$c0,$80,$00,$00 ; 17 sun bottom right
!byte $00,$08,$1c,$3e,$7f,$ff,$ff,$ff ; 18 mountain peak
!byte $00,$00,$00,$08,$1c,$3e,$7f,$ff ; 19 low mountain
!fill 1888,0
; 24x21 striped sun sprite, displayed double width and height.
* = $3800
!byte $00,$7e,$00,$01,$ff,$80,$03,$ff,$c0,$07,$ff,$e0
!byte $0f,$ff,$f0,$1f,$ff,$f8,$3f,$ff,$fc,$3f,$ff,$fc
!byte $00,$00,$00,$3f,$ff,$fc,$00,$00,$00,$1f,$ff,$f8
!byte $00,$00,$00,$0f,$ff,$f0,$00,$00,$00,$07,$ff,$e0
!byte $00,$00,$00,$03,$ff,$c0,$01,$ff,$80,$00,$7e,$00
!byte $00,$3c,$00,$00