Skip to content
Game 1 Unit 31 of 64 1 hr learning time

Results Screen Enhancements

Enhancing the results screen with performance grades (S/A/B/C/D), accuracy percentage calculation, and best streak display. Players now receive clear feedback on their performance quality.

48% of SID Symphony

Numbers tell part of the story. A score of 4,350 means nothing without context—was that good? Great? Mediocre? Performance grades give players instant understanding of how well they played.

This unit adds letter grades (S/A/B/C/D), accuracy percentage calculation, and best streak tracking to the results screen.

Run It

Assemble and run:

acme -f cbm -o symphony.prg symphony.asm

Unit 31 Screenshot

Complete a song and the results screen now shows your performance grade alongside statistics.

Performance grades — S/A/B/C/D based on accuracy percentage

Grade Thresholds

Letter grades based on perfect hit percentage:

; Performance grade thresholds (based on accuracy percentage)
GRADE_S_THRESHOLD = 95          ; 95%+ perfect rate = S rank
GRADE_A_THRESHOLD = 85          ; 85%+ = A rank
GRADE_B_THRESHOLD = 70          ; 70%+ = B rank
GRADE_C_THRESHOLD = 50          ; 50%+ = C rank
                                ; Below 50% = D rank

S rank requires near-perfection—95% of all hits must be perfect timing. This creates aspirational goals for dedicated players.

Grade Variables

Two variables store the calculated grade:

current_grade = $23             ; Current performance grade letter (screen code)
current_grade_col = $24         ; Colour for current grade

Screen codes and colours are calculated together so display is immediate.

Percentage Calculation

Calculating (perfect_count × 100) / notes_hit requires multiplication and division in 8-bit assembly:

calculate_grade:
            ; If no notes hit, give D grade
            lda notes_hit
            bne calc_grade_continue
            lda #'d'
            sta current_grade
            lda #2                  ; Red
            sta current_grade_col
            rts

calc_grade_continue:
            ; Multiply perfect_count by 100
            ; Using shifts: 100 = 64 + 32 + 4
            lda perfect_count
            sta work_lo
            lda #0
            sta work_hi

We check for zero notes first to avoid division by zero.

Multiplication by Shifting

Multiplying by 100 uses binary decomposition:

            ; Multiply by 100 (64 + 32 + 4 = 100)
            ; work = perfect * 64
            asl work_lo
            rol work_hi             ; *2
            asl work_lo
            rol work_hi             ; *4
            ...
            asl work_lo
            rol work_hi             ; *64

Six shifts create ×64, then we add ×32 and ×4 to get ×100. This avoids slow multiplication loops.

Division by Subtraction

For small dividends, repeated subtraction is simplest:

grade_div_loop:
            ; Check if work >= notes_hit
            lda work_hi
            bne grade_can_sub       ; High byte nonzero, can subtract
            lda work_lo
            cmp notes_hit
            bcc grade_div_done      ; work < notes_hit, done
grade_can_sub:
            ; Subtract notes_hit from work
            lda work_lo
            sec
            sbc notes_hit
            sta work_lo
            lda work_hi
            sbc #0
            sta work_hi
            inc ZP_TEMP             ; Increment percentage
            lda ZP_TEMP
            cmp #101                ; Cap at 100%
            bcc grade_div_loop

Each subtraction increments the quotient. We cap at 100% to prevent overflow from rounding.

Grade Determination

Cascading comparisons find the correct grade:

            lda ZP_TEMP
            cmp #GRADE_S_THRESHOLD
            bcs grade_is_s
            cmp #GRADE_A_THRESHOLD
            bcs grade_is_a
            cmp #GRADE_B_THRESHOLD
            bcs grade_is_b
            cmp #GRADE_C_THRESHOLD
            bcs grade_is_c
            ; Below C threshold = D

Branch-if-carry-set (BCS) checks “greater than or equal” after a compare.

Grade Colours

Each grade has a distinct colour for instant recognition:

grade_is_s:
            lda #'s'
            sta current_grade
            lda #7                  ; Yellow
            sta current_grade_col
            rts

grade_is_a:
            lda #'a'
            sta current_grade
            lda #5                  ; Green
            sta current_grade_col
            rts
GradeColourMeaning
SYellowSuperb—near perfect
AGreenExcellent
BCyanGood
CLight greyAverage
DRedNeeds practice

Grade Display

The grade appears prominently on the results screen:

draw_grade:
            ; Calculate and display performance grade
            jsr calculate_grade
            ; Display grade label
            ldx #0
draw_grade_label:
            lda grade_label,x
            beq draw_grade_letter
            sta SCREEN + (7 * 40) + 14,x
            lda #7
            sta COLRAM + (7 * 40) + 14,x
            inx
            jmp draw_grade_label

draw_grade_letter:
            lda current_grade
            sta SCREEN + (7 * 40) + 22
            ldx current_grade_col
            stx COLRAM + (7 * 40) + 22

The grade letter stands alone, coloured for impact.

Best Streak Display

We also show the best perfect streak achieved:

            ; Draw best streak on row 19
            ldx #0
draw_streak_label:
            lda streak_label,x
            beq draw_streak_value
            sta SCREEN + (19 * 40) + 12,x
            lda #3                  ; Cyan
            sta COLRAM + (19 * 40) + 12,x
            inx
            jmp draw_streak_label

Players see their longest run of consecutive perfect hits—a secondary goal beyond the overall grade.

Results Screen Layout

The enhanced results screen shows:

RowContent
5”COMPLETE” header
7Grade: S/A/B/C/D
9Final Score
11Perfect count
13Miss count
15Max combo
17High score
19Best streak
21”Press fire” prompt

Every statistic that matters is visible at a glance.

Text Data

New labels for the enhanced display:

grade_label:
            !scr "grade:"
            !byte 0

streak_label:
            !scr "best streak:"
            !byte 0

Consistent formatting with existing labels maintains visual harmony.

The Complete Code

; ============================================================================
; SID SYMPHONY - Unit 31: Results Screen Enhancements
; ============================================================================
; Enhancing the results screen with performance grades (S/A/B/C/D), accuracy
; percentage, best streak display, and improved statistics layout.
;
; New concepts: Grade calculation, percentage display, enhanced UI layout
;
; Controls: Z = Track 1, X = Track 2, C = Track 3
;           Up/Down = Song selection, Left/Right = Difficulty
;           P = Toggle practice mode, +/- = Speed adjustment
;           E = Toggle endless mode
;           Fire/Space = Start game
; ============================================================================

; ============================================================================
; CUSTOMISATION SECTION
; ============================================================================

; Screenshot mode - set to 1 to skip title and show menu immediately
; Used for automated screenshot capture. Override with: acme -DSCREENSHOT_MODE=1
!ifndef SCREENSHOT_MODE { SCREENSHOT_MODE = 0 }

; SID Voice Settings (for note playback)
VOICE1_WAVE = $21               ; Sawtooth for track 1
VOICE2_WAVE = $41               ; Pulse for track 2
VOICE3_WAVE = $11               ; Triangle for track 3

VOICE1_FREQ = $1C
VOICE2_FREQ = $0E
VOICE3_FREQ = $07

VOICE_AD    = $09               ; Attack=0, Decay=9
VOICE_SR    = $00               ; Sustain=0, Release=0
PULSE_WIDTH = $08

; Perfect hit sound - bright and satisfying
PERFECT_SFX_FREQ  = $30         ; Higher pitch
PERFECT_SFX_WAVE  = $21         ; Sawtooth (bright)
PERFECT_SFX_AD    = $08         ; Fast attack, medium decay
PERFECT_SFX_SR    = $00         ; No sustain

; Good hit sound - positive but lesser
GOOD_SFX_FREQ     = $20         ; Lower pitch than perfect
GOOD_SFX_WAVE     = $11         ; Triangle (softer)
GOOD_SFX_AD       = $0A         ; Slightly slower decay
GOOD_SFX_SR       = $00         ; No sustain

; Miss sound settings - harsh buzz
MISS_FREQ   = $08
MISS_WAVE   = $81               ; Noise
MISS_AD     = $00               ; Instant attack
MISS_SR     = $A0               ; Quick sustain, fast release

; Menu sound settings
MENU_SELECT_FREQ  = $18         ; Menu click pitch
MENU_SELECT_WAVE  = $41         ; Pulse for click
MENU_SELECT_AD    = $00         ; Instant
MENU_SELECT_SR    = $80         ; Very short

MENU_MOVE_FREQ    = $10         ; Lower pitch for cursor move
MENU_MOVE_WAVE    = $11         ; Triangle (soft)
MENU_MOVE_AD      = $00         ; Instant
MENU_MOVE_SR      = $40         ; Very short

; Visual Settings
BORDER_COL  = 0
BG_COL      = 0

TRACK1_NOTE_COL = 10            ; Light red for high track
TRACK2_NOTE_COL = 13            ; Light green for mid track
TRACK3_NOTE_COL = 14            ; Light blue for low track

TRACK_LINE_COL = 11             ; Grey for track lines
HIT_ZONE_COL = 7                ; Yellow for hit zone

HIT_COL     = 1
PERFECT_COL = 1                 ; White flash for perfect
GOOD_COL    = 7                 ; Yellow flash for good
MISS_COL    = 2                 ; Red flash for miss

HEALTH_COL  = 5                 ; Green for health bar
PROGRESS_COL = 3                ; Cyan for progress bar
COMBO_COL   = 13                ; Light green for combo

; Title screen colours
TITLE_COL   = 1                 ; White for title
SUBTITLE_COL = 11               ; Grey for subtitle
MENU_COL    = 7                 ; Yellow for menu items
CURSOR_COL  = 1                 ; White for cursor
EASY_COL    = 5                 ; Green for Easy
NORMAL_COL  = 7                 ; Yellow for Normal
HARD_COL    = 2                 ; Red for Hard

; ============================================================================
; SCORING SETTINGS (BALANCED)
; ============================================================================

PERFECT_SCORE = 100             ; Perfect hit value
GOOD_SCORE    = 50              ; Good hit value

; Streak bonuses (awarded every N consecutive perfects)
STREAK_THRESHOLD = 5            ; Perfect streak bonus every 5 perfects
STREAK_BONUS  = 50              ; Bonus points per streak milestone

; Section completion bonuses (25%, 50%, 75%, 100%)
SECTION_BONUS = 100             ; Bonus per section completed

; Full combo bonus (no misses)
FULL_COMBO_BONUS = 200          ; Bonus if no misses entire song

; Performance grade thresholds (based on accuracy percentage)
; Accuracy = (perfect_count * 100 + good_count * 50) / total_notes
GRADE_S_THRESHOLD = 95          ; 95%+ perfect rate = S rank
GRADE_A_THRESHOLD = 85          ; 85%+ = A rank
GRADE_B_THRESHOLD = 70          ; 70%+ = B rank
GRADE_C_THRESHOLD = 50          ; 50%+ = C rank
                                ; Below 50% = D rank

; ============================================================================
; COMBO SETTINGS (BALANCED)
; ============================================================================

COMBO_TIER_2  = 10              ; 2x multiplier at 10 hits
COMBO_TIER_3  = 25              ; 3x multiplier at 25 hits
COMBO_TIER_4  = 50              ; 4x multiplier at 50 hits

; Combo border colours (escalating intensity)
COMBO_BORDER_0 = 0              ; Black - no combo
COMBO_BORDER_1 = 11             ; Dark grey - building
COMBO_BORDER_2 = 5              ; Green - 2x (tier 2)
COMBO_BORDER_3 = 7              ; Yellow - 3x (tier 3)
COMBO_BORDER_4 = 2              ; Red - 4x (tier 4 / fire!)

; ============================================================================
; HEALTH SETTINGS (BASE VALUES - MODIFIED BY DIFFICULTY)
; ============================================================================

HEALTH_MAX    = 64              ; Maximum health
HEALTH_START  = 32              ; Start at half health
HEALTH_PERFECT = 4              ; Gain 4 for perfect
HEALTH_GOOD   = 2               ; Gain 2 for good

; Health loss per difficulty
HEALTH_MISS_EASY   = 4          ; Forgiving
HEALTH_MISS_NORMAL = 8          ; Balanced
HEALTH_MISS_HARD   = 12         ; Punishing

; ============================================================================
; HIT DETECTION SETTINGS (BASE - MODIFIED BY DIFFICULTY)
; ============================================================================

; Hit zone column range (where notes can be hit)
HIT_ZONE_START = 2              ; Leftmost hittable column
HIT_ZONE_END   = 6              ; Rightmost hittable column

; Perfect timing column (centre of hit zone)
PERFECT_COL_POS = 4

; Beat marker position (visual rhythm indicator)
HIT_ROW        = 20             ; Row for beat marker display

; Timing windows per difficulty (columns from perfect)
; Easy: Perfect ±2, Good ±4
; Normal: Perfect ±1, Good ±2
; Hard: Perfect ±0, Good ±1

PERFECT_WINDOW_EASY   = 2
PERFECT_WINDOW_NORMAL = 1
PERFECT_WINDOW_HARD   = 0

GOOD_WINDOW_EASY      = 4
GOOD_WINDOW_NORMAL    = 2
GOOD_WINDOW_HARD      = 1

; ============================================================================
; DIFFICULTY SETTINGS
; ============================================================================

DIFF_EASY   = 0
DIFF_NORMAL = 1
DIFF_HARD   = 2

; ============================================================================
; SONG SETTINGS
; ============================================================================

PROGRESS_WIDTH = 16             ; Progress bar width

; Per-song tempo (frames per beat)
TEMPO_SONG1   = 25              ; 120 BPM
TEMPO_SONG2   = 23              ; 130 BPM
TEMPO_SONG3   = 20              ; 150 BPM
TEMPO_SONG4   = 24              ; 125 BPM (syncopated feel)
TEMPO_SONG5   = 22              ; 136 BPM (chord-focused)
TEMPO_SONG6   = 23              ; 130 BPM (balanced for mixed techniques)

; Per-song length (beats)
LENGTH_SONG1  = 64
LENGTH_SONG2  = 64
LENGTH_SONG3  = 64
LENGTH_SONG4  = 64
LENGTH_SONG5  = 64
LENGTH_SONG6  = 64

; ============================================================================
; MENU SETTINGS
; ============================================================================

NUM_SONGS     = 6
KEY_DELAY     = 10              ; Frames between key repeats

; ============================================================================
; PRACTICE MODE SETTINGS
; ============================================================================

; Speed multipliers (stored as frame adjustment)
; Higher value = slower (more frames per beat)
SPEED_HALF    = 2               ; 0.5x speed (double frames)
SPEED_3QRTR   = 1               ; 0.75x speed (4/3 frames, approx +1)
SPEED_NORMAL  = 0               ; 1.0x speed (no adjustment)

NUM_SPEEDS    = 3

PRACTICE_COL  = 3               ; Cyan for practice mode indicator

; ============================================================================
; GAME STATES
; ============================================================================

STATE_TITLE   = 0               ; Title screen
STATE_MENU    = 1               ; Song selection menu
STATE_PLAYING = 2               ; Gameplay
STATE_RESULTS = 3               ; Success results
STATE_GAMEOVER = 4              ; Failure game over

; ============================================================================
; MEMORY MAP
; ============================================================================

SCREEN      = $0400
COLRAM      = $D800
BORDER      = $D020
BGCOL       = $D021
CHARPTR     = $D018

CHARSET     = $3000

; SID registers
SID         = $D400
SID_V1_FREQ_LO = $D400
SID_V1_FREQ_HI = $D401
SID_V1_PWHI = $D403
SID_V1_CTRL = $D404
SID_V1_AD   = $D405
SID_V1_SR   = $D406

SID_V2_FREQ_LO = $D407
SID_V2_FREQ_HI = $D408
SID_V2_PWHI = $D40A
SID_V2_CTRL = $D40B
SID_V2_AD   = $D40C
SID_V2_SR   = $D40D

SID_V3_FREQ_LO = $D40E
SID_V3_FREQ_HI = $D40F
SID_V3_PWHI = $D411
SID_V3_CTRL = $D412
SID_V3_AD   = $D413
SID_V3_SR   = $D414

SID_VOLUME  = $D418

; CIA keyboard and joystick
CIA1_PRA    = $DC00
CIA1_PRB    = $DC01

; Track positions (balanced spacing)
TRACK1_ROW  = 8                 ; High track
TRACK2_ROW  = 12                ; Mid track
TRACK3_ROW  = 16                ; Low track

; HUD positions
HEALTH_ROW  = 23                ; Health bar row
PROGRESS_ROW = 24               ; Progress bar row

; Combo display position
COMBO_ROW   = 2                 ; Combo display row

; Hit zone
HIT_ZONE_COLUMN = 3             ; Where notes are hit (visual)

; Custom character codes
CHAR_NOTE   = 128
CHAR_TRACK  = 129
CHAR_HITZONE = 130
CHAR_SPACE  = 32
CHAR_BAR_FULL = 131
CHAR_BAR_EMPTY = 132
CHAR_CURSOR = 62                ; > character for menu cursor
CHAR_ARROW_L = 60               ; < for difficulty
CHAR_ARROW_R = 62               ; > for difficulty

; Note settings
MAX_NOTES   = 8                 ; Maximum simultaneous notes
NOTE_SPAWN_COL = 37             ; Where notes appear

; Timing
END_DELAY_FRAMES = 75           ; 1.5 seconds after song ends

; Zero page
ZP_PTR      = $FB
ZP_PTR_HI   = $FC
ZP_TEMP     = $FD               ; Temporary calculation

; Variables
frame_count = $02
beat_count  = $03
song_pos    = $04
song_pos_hi = $05
temp_track  = $06
key_pressed = $07
hit_quality = $08
border_flash = $09
miss_track  = $0A
game_state  = $0B
hit_note_freq = $0C
song_beat   = $0D
song_ended  = $0E
end_delay   = $0F
cursor_pos  = $10               ; Menu cursor position
key_delay_count = $11           ; Key repeat delay counter
selected_song = $12             ; Which song to play
frames_per_beat = $13           ; Current song's tempo
song_length = $14               ; Current song's length
difficulty  = $15               ; 0=Easy, 1=Normal, 2=Hard
perfect_window = $16            ; Current perfect timing window
good_window = $17               ; Current good timing window
health_miss_amt = $18           ; Health loss on miss
practice_mode = $19             ; 0=Normal play, 1=Practice mode
speed_setting = $1A             ; 0=0.5x, 1=0.75x, 2=1.0x
bg_flash      = $1B             ; Background flash countdown (visual polish)
beat_pulse    = $1C             ; Beat marker pulse state
endless_mode  = $1D             ; 0=Normal (song ends), 1=Endless (loops)
loop_count    = $1E             ; Number of loops completed in endless mode
perfect_streak = $1F            ; Current consecutive perfect hits
best_streak   = $20             ; Best perfect streak this song
section_bonus = $21             ; Accumulated section bonus points
notes_hit     = $22             ; Total notes hit (for accuracy calc)
current_grade = $23             ; Current performance grade letter (screen code)
current_grade_col = $24         ; Colour for current grade

; ----------------------------------------------------------------------------
; BASIC Stub
; ----------------------------------------------------------------------------

            * = $0801

            !byte $0C, $08
            !byte $0A, $00
            !byte $9E
            !text "2064"
            !byte $00
            !byte $00, $00

; ----------------------------------------------------------------------------
; Main Program
; ----------------------------------------------------------------------------

            * = $0810

start:
            jsr copy_charset
            jsr init_sid

            ; Start with Normal difficulty
            lda #DIFF_NORMAL
            sta difficulty

            ; Practice mode off, normal speed
            lda #0
            sta practice_mode
            lda #NUM_SPEEDS-1       ; Start at 1.0x (normal speed)
            sta speed_setting

!if SCREENSHOT_MODE = 1 {
            ; Screenshot mode: skip title, go to menu
            jsr show_menu
            lda #STATE_MENU
            sta game_state
} else {
            ; Normal mode: show title screen
            jsr show_title
            lda #STATE_TITLE
            sta game_state
}

main_loop:
            lda #$FF
wait_raster:
            cmp $D012
            bne wait_raster

            lda game_state
            cmp #STATE_TITLE
            beq do_title
            cmp #STATE_MENU
            beq do_menu
            cmp #STATE_PLAYING
            beq do_playing
            cmp #STATE_RESULTS
            beq do_results
            jmp do_gameover

do_title:
            jsr update_title
            jmp main_loop

do_menu:
            jsr update_menu
            jmp main_loop

do_playing:
            jsr update_playing
            jmp main_loop

do_results:
            jsr update_results
            jmp main_loop

do_gameover:
            jsr update_gameover
            jmp main_loop

; ----------------------------------------------------------------------------
; Show Title Screen
; ----------------------------------------------------------------------------

show_title:
            ; Clear screen
            lda #BORDER_COL
            sta BORDER
            lda #BG_COL
            sta BGCOL

            ldx #0
            lda #CHAR_SPACE
clear_title:
            sta SCREEN,x
            sta SCREEN+$100,x
            sta SCREEN+$200,x
            sta SCREEN+$2E8,x
            inx
            bne clear_title

            ; Draw big title "SID SYMPHONY"
            ldx #0
draw_title_text:
            lda title_big,x
            beq draw_title_done
            sta SCREEN + (8 * 40) + 14,x
            lda #TITLE_COL
            sta COLRAM + (8 * 40) + 14,x
            inx
            jmp draw_title_text
draw_title_done:

            ; Draw subtitle "A RHYTHM GAME"
            ldx #0
draw_subtitle:
            lda subtitle_text,x
            beq draw_subtitle_done
            sta SCREEN + (10 * 40) + 13,x
            lda #SUBTITLE_COL
            sta COLRAM + (10 * 40) + 13,x
            inx
            jmp draw_subtitle
draw_subtitle_done:

            ; Draw controls
            ldx #0
draw_controls:
            lda controls_text,x
            beq draw_controls_done
            sta SCREEN + (14 * 40) + 11,x
            lda #11
            sta COLRAM + (14 * 40) + 11,x
            inx
            jmp draw_controls
draw_controls_done:

            ; Draw track info
            ldx #0
draw_track_info:
            lda track_info,x
            beq draw_track_done
            sta SCREEN + (16 * 40) + 9,x
            lda #11
            sta COLRAM + (16 * 40) + 9,x
            inx
            jmp draw_track_info
draw_track_done:

            ; Draw "PRESS FIRE TO START"
            ldx #0
draw_press_fire:
            lda press_fire_text,x
            beq draw_press_done
            sta SCREEN + (20 * 40) + 10,x
            lda #7              ; Yellow
            sta COLRAM + (20 * 40) + 10,x
            inx
            jmp draw_press_fire
draw_press_done:

            rts

title_big:
            !scr "sid symphony"
            !byte 0

subtitle_text:
            !scr "a rhythm game"
            !byte 0

controls_text:
            !scr "controls: z / x / c"
            !byte 0

track_info:
            !scr "hit notes as they reach"
            !byte 0

press_fire_text:
            !scr "press fire to start"
            !byte 0

; ----------------------------------------------------------------------------
; Update Title State
; ----------------------------------------------------------------------------

update_title:
            ; Check for fire button (joystick port 2)
            lda CIA1_PRA
            and #$10            ; Bit 4 = fire
            beq title_fire_pressed

            ; Also check space bar as alternative
            lda #$7F
            sta CIA1_PRA
            lda CIA1_PRB
            and #$10            ; Space
            beq title_fire_pressed

            ; No input - stay on title
            lda #$FF
            sta CIA1_PRA
            rts

title_fire_pressed:
            lda #$FF
            sta CIA1_PRA

            ; Play menu select sound
            jsr play_menu_select

            ; Go to song selection menu
            jsr show_menu
            lda #STATE_MENU
            sta game_state
            rts

; ----------------------------------------------------------------------------
; Show Song Selection Menu
; ----------------------------------------------------------------------------

show_menu:
            ; Clear screen
            ldx #0
            lda #CHAR_SPACE
clear_menu:
            sta SCREEN,x
            sta SCREEN+$100,x
            sta SCREEN+$200,x
            sta SCREEN+$2E8,x
            inx
            bne clear_menu

            ; Initialise cursor
            lda #0
            sta cursor_pos
            sta key_delay_count

            ; Draw menu title
            ldx #0
draw_menu_title:
            lda menu_title,x
            beq draw_menu_title_done
            sta SCREEN + (3 * 40) + 12,x
            lda #TITLE_COL
            sta COLRAM + (3 * 40) + 12,x
            inx
            jmp draw_menu_title
draw_menu_title_done:

            ; Draw song list
            jsr draw_song_list

            ; Draw difficulty section
            jsr draw_difficulty

            ; Draw practice mode section
            jsr draw_practice_mode

            ; Draw instructions on row 24 (below Endless on row 23)
            ldx #0
draw_menu_instr:
            lda menu_instructions,x
            beq draw_menu_instr_done
            sta SCREEN + (24 * 40) + 1,x
            lda #SUBTITLE_COL
            sta COLRAM + (24 * 40) + 1,x
            inx
            jmp draw_menu_instr
draw_menu_instr_done:

            rts

menu_title:
            !scr "select a song"
            !byte 0

menu_instructions:
            !scr "up/down:song  left/right:difficulty"
            !byte 0

practice_instructions:
            !scr "p:practice  +/-:speed"
            !byte 0

; ----------------------------------------------------------------------------
; Draw Song List
; ----------------------------------------------------------------------------

draw_song_list:
            ; Song 1: "First Steps" (120 BPM)
            ldx #0
draw_song1:
            lda song1_name,x
            beq draw_song1_done
            sta SCREEN + (8 * 40) + 14,x
            lda #MENU_COL
            sta COLRAM + (8 * 40) + 14,x
            inx
            jmp draw_song1
draw_song1_done:

            ; Song 1 tempo info
            ldx #0
draw_tempo1:
            lda song1_tempo_text,x
            beq draw_tempo1_done
            sta SCREEN + (8 * 40) + 27,x
            lda #SUBTITLE_COL
            sta COLRAM + (8 * 40) + 27,x
            inx
            jmp draw_tempo1
draw_tempo1_done:

            ; Song 2: "Upbeat Groove" (130 BPM)
            ldx #0
draw_song2:
            lda song2_name,x
            beq draw_song2_done
            sta SCREEN + (10 * 40) + 14,x
            lda #MENU_COL
            sta COLRAM + (10 * 40) + 14,x
            inx
            jmp draw_song2
draw_song2_done:

            ; Song 2 tempo info
            ldx #0
draw_tempo2:
            lda song2_tempo_text,x
            beq draw_tempo2_done
            sta SCREEN + (10 * 40) + 27,x
            lda #SUBTITLE_COL
            sta COLRAM + (10 * 40) + 27,x
            inx
            jmp draw_tempo2
draw_tempo2_done:

            ; Song 3: "Speed Rush" (150 BPM)
            ldx #0
draw_song3:
            lda song3_name,x
            beq draw_song3_done
            sta SCREEN + (12 * 40) + 14,x
            lda #MENU_COL
            sta COLRAM + (12 * 40) + 14,x
            inx
            jmp draw_song3
draw_song3_done:

            ; Song 3 tempo info
            ldx #0
draw_tempo3:
            lda song3_tempo_text,x
            beq draw_tempo3_done
            sta SCREEN + (12 * 40) + 27,x
            lda #SUBTITLE_COL
            sta COLRAM + (12 * 40) + 27,x
            inx
            jmp draw_tempo3
draw_tempo3_done:

            ; Song 4: "Off-Beat" (125 BPM)
            ldx #0
draw_song4:
            lda song4_name,x
            beq draw_song4_done
            sta SCREEN + (14 * 40) + 14,x
            lda #MENU_COL
            sta COLRAM + (14 * 40) + 14,x
            inx
            jmp draw_song4
draw_song4_done:

            ; Song 4 tempo info
            ldx #0
draw_tempo4:
            lda song4_tempo_text,x
            beq draw_tempo4_done
            sta SCREEN + (14 * 40) + 27,x
            lda #SUBTITLE_COL
            sta COLRAM + (14 * 40) + 27,x
            inx
            jmp draw_tempo4
draw_tempo4_done:

            ; Song 5: "Chordstorm" (136 BPM)
            ldx #0
draw_song5:
            lda song5_name,x
            beq draw_song5_done
            sta SCREEN + (16 * 40) + 14,x
            lda #MENU_COL
            sta COLRAM + (16 * 40) + 14,x
            inx
            jmp draw_song5
draw_song5_done:

            ; Song 5 tempo info
            ldx #0
draw_tempo5:
            lda song5_tempo_text,x
            beq draw_tempo5_done
            sta SCREEN + (16 * 40) + 27,x
            lda #SUBTITLE_COL
            sta COLRAM + (16 * 40) + 27,x
            inx
            jmp draw_tempo5
draw_tempo5_done:

            ; Song 6: "Fusion" (130 BPM)
            ldx #0
draw_song6:
            lda song6_name,x
            beq draw_song6_done
            sta SCREEN + (18 * 40) + 14,x
            lda #MENU_COL
            sta COLRAM + (18 * 40) + 14,x
            inx
            jmp draw_song6
draw_song6_done:

            ; Song 6 tempo info
            ldx #0
draw_tempo6:
            lda song6_tempo_text,x
            beq draw_tempo6_done
            sta SCREEN + (18 * 40) + 27,x
            lda #SUBTITLE_COL
            sta COLRAM + (18 * 40) + 27,x
            inx
            jmp draw_tempo6
draw_tempo6_done:

            ; Draw cursor at current position
            jsr draw_cursor
            rts

; Song names and info
song1_name:
            !scr "first steps"
            !byte 0

song1_tempo_text:
            !scr "(120 bpm)"
            !byte 0

song2_name:
            !scr "upbeat groove"
            !byte 0

song2_tempo_text:
            !scr "(130 bpm)"
            !byte 0

song3_name:
            !scr "speed rush"
            !byte 0

song3_tempo_text:
            !scr "(150 bpm)"
            !byte 0

song4_name:
            !scr "off-beat"
            !byte 0

song4_tempo_text:
            !scr "(125 bpm)"
            !byte 0

song5_name:
            !scr "chordstorm"
            !byte 0

song5_tempo_text:
            !scr "(136 bpm)"
            !byte 0

song6_name:
            !scr "fusion"
            !byte 0

song6_tempo_text:
            !scr "(130 bpm)"
            !byte 0

; ----------------------------------------------------------------------------
; Draw Difficulty Selection
; ----------------------------------------------------------------------------

draw_difficulty:
            ; Draw "DIFFICULTY:" label
            ldx #0
draw_diff_label:
            lda diff_label,x
            beq draw_diff_label_done
            sta SCREEN + (20 * 40) + 10,x
            lda #TITLE_COL
            sta COLRAM + (20 * 40) + 10,x
            inx
            jmp draw_diff_label
draw_diff_label_done:

            ; Draw arrows
            lda #CHAR_ARROW_L
            sta SCREEN + (20 * 40) + 22
            lda #SUBTITLE_COL
            sta COLRAM + (20 * 40) + 22

            lda #CHAR_ARROW_R
            sta SCREEN + (20 * 40) + 30
            lda #SUBTITLE_COL
            sta COLRAM + (20 * 40) + 30

            ; Draw current difficulty
            jsr draw_current_difficulty

            rts

diff_label:
            !scr "difficulty:"
            !byte 0

; ----------------------------------------------------------------------------
; Draw Current Difficulty Name
; ----------------------------------------------------------------------------

draw_current_difficulty:
            ; Clear difficulty name area
            lda #CHAR_SPACE
            sta SCREEN + (20 * 40) + 24
            sta SCREEN + (20 * 40) + 25
            sta SCREEN + (20 * 40) + 26
            sta SCREEN + (20 * 40) + 27
            sta SCREEN + (20 * 40) + 28

            lda difficulty
            cmp #DIFF_EASY
            beq draw_diff_easy
            cmp #DIFF_NORMAL
            beq draw_diff_normal
            jmp draw_diff_hard

draw_diff_easy:
            ldx #0
draw_easy_text:
            lda easy_text,x
            beq draw_diff_done
            sta SCREEN + (20 * 40) + 24,x
            lda #EASY_COL
            sta COLRAM + (20 * 40) + 24,x
            inx
            jmp draw_easy_text

draw_diff_normal:
            ldx #0
draw_normal_text:
            lda normal_text,x
            beq draw_diff_done
            sta SCREEN + (20 * 40) + 24,x
            lda #NORMAL_COL
            sta COLRAM + (20 * 40) + 24,x
            inx
            jmp draw_normal_text

draw_diff_hard:
            ldx #0
draw_hard_text:
            lda hard_text,x
            beq draw_diff_done
            sta SCREEN + (20 * 40) + 24,x
            lda #HARD_COL
            sta COLRAM + (20 * 40) + 24,x
            inx
            jmp draw_hard_text

draw_diff_done:
            rts

easy_text:
            !scr "easy"
            !byte 0

normal_text:
            !scr "normal"
            !byte 0

hard_text:
            !scr "hard"
            !byte 0

; ----------------------------------------------------------------------------
; Draw Practice Mode Section
; ----------------------------------------------------------------------------

draw_practice_mode:
            ; Draw "PRACTICE:" label
            ldx #0
draw_prac_label:
            lda prac_label,x
            beq draw_prac_label_done
            sta SCREEN + (21 * 40) + 12,x
            lda #TITLE_COL
            sta COLRAM + (21 * 40) + 12,x
            inx
            jmp draw_prac_label
draw_prac_label_done:

            ; Draw current state
            jsr draw_practice_state

            ; Draw speed section
            ldx #0
draw_speed_label:
            lda speed_label,x
            beq draw_speed_label_done
            sta SCREEN + (22 * 40) + 14,x
            lda #TITLE_COL
            sta COLRAM + (22 * 40) + 14,x
            inx
            jmp draw_speed_label
draw_speed_label_done:

            jsr draw_speed_state

            ; Draw endless section
            ldx #0
draw_endless_label:
            lda endless_label,x
            beq draw_endless_label_done
            sta SCREEN + (23 * 40) + 13,x
            lda #TITLE_COL
            sta COLRAM + (23 * 40) + 13,x
            inx
            jmp draw_endless_label
draw_endless_label_done:

            jsr draw_endless_state
            rts

prac_label:
            !scr "practice:"
            !byte 0

speed_label:
            !scr "speed:"
            !byte 0

endless_label:
            !scr "endless:"
            !byte 0

; Draw ON/OFF based on practice_mode
draw_practice_state:
            lda practice_mode
            beq draw_prac_off
            ; Draw ON
            lda #'o'
            sta SCREEN + (21 * 40) + 22
            lda #'n'
            sta SCREEN + (21 * 40) + 23
            lda #CHAR_SPACE
            sta SCREEN + (21 * 40) + 24
            lda #PRACTICE_COL
            sta COLRAM + (21 * 40) + 22
            sta COLRAM + (21 * 40) + 23
            rts

draw_prac_off:
            ; Draw OFF
            lda #'o'
            sta SCREEN + (21 * 40) + 22
            lda #'f'
            sta SCREEN + (21 * 40) + 23
            lda #'f'
            sta SCREEN + (21 * 40) + 24
            lda #SUBTITLE_COL
            sta COLRAM + (21 * 40) + 22
            sta COLRAM + (21 * 40) + 23
            sta COLRAM + (21 * 40) + 24
            rts

; Draw speed (0.5x / 0.75x / 1.0x)
draw_speed_state:
            ; Clear speed area
            lda #CHAR_SPACE
            sta SCREEN + (22 * 40) + 21
            sta SCREEN + (22 * 40) + 22
            sta SCREEN + (22 * 40) + 23
            sta SCREEN + (22 * 40) + 24
            sta SCREEN + (22 * 40) + 25

            lda speed_setting
            beq draw_speed_half
            cmp #1
            beq draw_speed_3qrtr
            jmp draw_speed_normal

draw_speed_half:
            ldx #0
draw_half_text:
            lda half_text,x
            beq draw_speed_done
            sta SCREEN + (22 * 40) + 21,x
            lda #EASY_COL           ; Green - slowest
            sta COLRAM + (22 * 40) + 21,x
            inx
            jmp draw_half_text

draw_speed_3qrtr:
            ldx #0
draw_3qrtr_text:
            lda qrtr_text,x
            beq draw_speed_done
            sta SCREEN + (22 * 40) + 21,x
            lda #NORMAL_COL         ; Yellow - medium
            sta COLRAM + (22 * 40) + 21,x
            inx
            jmp draw_3qrtr_text

draw_speed_normal:
            ldx #0
draw_normal_speed_text:
            lda full_text,x
            beq draw_speed_done
            sta SCREEN + (22 * 40) + 21,x
            lda #TITLE_COL          ; White - full speed
            sta COLRAM + (22 * 40) + 21,x
            inx
            jmp draw_normal_speed_text

draw_speed_done:
            rts

half_text:
            !scr "0.5x"
            !byte 0

qrtr_text:
            !scr "0.75x"
            !byte 0

full_text:
            !scr "1.0x"
            !byte 0

; ----------------------------------------------------------------------------
; Draw Endless Mode State
; ----------------------------------------------------------------------------

draw_endless_state:
            lda endless_mode
            beq draw_endless_off
            ; Draw ON in cyan
            lda #'o'
            sta SCREEN + (23 * 40) + 22
            lda #'n'
            sta SCREEN + (23 * 40) + 23
            lda #CHAR_SPACE
            sta SCREEN + (23 * 40) + 24
            lda #3                  ; Cyan
            sta COLRAM + (23 * 40) + 22
            sta COLRAM + (23 * 40) + 23
            rts

draw_endless_off:
            ; Draw OFF in grey
            lda #'o'
            sta SCREEN + (23 * 40) + 22
            lda #'f'
            sta SCREEN + (23 * 40) + 23
            lda #'f'
            sta SCREEN + (23 * 40) + 24
            lda #SUBTITLE_COL
            sta COLRAM + (23 * 40) + 22
            sta COLRAM + (23 * 40) + 23
            sta COLRAM + (23 * 40) + 24
            rts

; ----------------------------------------------------------------------------
; Draw Menu Cursor
; ----------------------------------------------------------------------------

draw_cursor:
            ; Clear all cursor positions
            lda #CHAR_SPACE
            sta SCREEN + (8 * 40) + 12
            sta SCREEN + (10 * 40) + 12
            sta SCREEN + (12 * 40) + 12
            sta SCREEN + (14 * 40) + 12
            sta SCREEN + (16 * 40) + 12
            sta SCREEN + (18 * 40) + 12

            ; Draw cursor at current position
            lda cursor_pos
            beq cursor_song1
            cmp #1
            beq cursor_song2
            cmp #2
            beq cursor_song3
            cmp #3
            beq cursor_song4
            cmp #4
            beq cursor_song5
            jmp cursor_song6

cursor_song1:
            lda #CHAR_CURSOR
            sta SCREEN + (8 * 40) + 12
            lda #CURSOR_COL
            sta COLRAM + (8 * 40) + 12
            rts

cursor_song2:
            lda #CHAR_CURSOR
            sta SCREEN + (10 * 40) + 12
            lda #CURSOR_COL
            sta COLRAM + (10 * 40) + 12
            rts

cursor_song3:
            lda #CHAR_CURSOR
            sta SCREEN + (12 * 40) + 12
            lda #CURSOR_COL
            sta COLRAM + (12 * 40) + 12
            rts

cursor_song4:
            lda #CHAR_CURSOR
            sta SCREEN + (14 * 40) + 12
            lda #CURSOR_COL
            sta COLRAM + (14 * 40) + 12
            rts

cursor_song5:
            lda #CHAR_CURSOR
            sta SCREEN + (16 * 40) + 12
            lda #CURSOR_COL
            sta COLRAM + (16 * 40) + 12
            rts

cursor_song6:
            lda #CHAR_CURSOR
            sta SCREEN + (18 * 40) + 12
            lda #CURSOR_COL
            sta COLRAM + (18 * 40) + 12
            rts

; ----------------------------------------------------------------------------
; Update Menu State
; ----------------------------------------------------------------------------

update_menu:
            ; Handle key delay
            lda key_delay_count
            beq menu_check_input
            dec key_delay_count
            jmp menu_check_fire

menu_check_input:
            ; Check joystick up
            lda CIA1_PRA
            and #$01            ; Bit 0 = up
            beq menu_up_pressed

            ; Check joystick down
            lda CIA1_PRA
            and #$02            ; Bit 1 = down
            beq menu_down_pressed

            ; Check joystick left (difficulty)
            lda CIA1_PRA
            and #$04            ; Bit 2 = left
            beq menu_left_pressed

            ; Check joystick right (difficulty)
            lda CIA1_PRA
            and #$08            ; Bit 3 = right
            beq menu_right_pressed

            jmp menu_check_fire

menu_up_pressed:
            lda cursor_pos
            beq menu_set_delay  ; Already at top
            dec cursor_pos
            jsr play_menu_move
            jsr draw_cursor
            jmp menu_set_delay

menu_down_pressed:
            lda cursor_pos
            cmp #NUM_SONGS-1
            bcs menu_set_delay  ; Already at bottom
            inc cursor_pos
            jsr play_menu_move
            jsr draw_cursor
            jmp menu_set_delay

menu_left_pressed:
            lda difficulty
            beq menu_set_delay  ; Already at Easy
            dec difficulty
            jsr play_menu_move
            jsr draw_current_difficulty
            jmp menu_set_delay

menu_right_pressed:
            lda difficulty
            cmp #DIFF_HARD
            bcs menu_set_delay  ; Already at Hard
            inc difficulty
            jsr play_menu_move
            jsr draw_current_difficulty
            jmp menu_set_delay

menu_set_delay:
            lda #KEY_DELAY
            sta key_delay_count

menu_check_keys:
            ; Check P key for practice toggle (row 1, column 5)
            lda #$FD                ; Row 1
            sta CIA1_PRA
            lda CIA1_PRB
            and #$20                ; Column 5 = P
            beq menu_p_pressed

            ; Check + key (row 0, column 0 = plus on keyboard)
            lda #$FE                ; Row 0
            sta CIA1_PRA
            lda CIA1_PRB
            and #$01                ; Column 0 = +
            beq menu_plus_pressed

            ; Check - key (row 5, column 3)
            lda #$DF                ; Row 5
            sta CIA1_PRA
            lda CIA1_PRB
            and #$08                ; Column 3 = -
            beq menu_minus_pressed

            ; Check E key for endless toggle (row 1, column 6)
            lda #$FD                ; Row 1
            sta CIA1_PRA
            lda CIA1_PRB
            and #$40                ; Column 6 = E
            beq menu_e_pressed

            jmp menu_check_fire

menu_e_pressed:
            ; Toggle endless mode
            lda endless_mode
            eor #1
            sta endless_mode
            jsr play_menu_move
            jsr draw_endless_state
            lda #KEY_DELAY
            sta key_delay_count
            jmp menu_check_fire

menu_p_pressed:
            ; Toggle practice mode
            lda practice_mode
            eor #1
            sta practice_mode
            jsr play_menu_move
            jsr draw_practice_state
            lda #KEY_DELAY
            sta key_delay_count
            jmp menu_check_fire

menu_plus_pressed:
            ; Increase speed (towards 1.0x)
            lda speed_setting
            cmp #NUM_SPEEDS-1
            bcs menu_check_fire     ; Already at max
            inc speed_setting
            jsr play_menu_move
            jsr draw_speed_state
            lda #KEY_DELAY
            sta key_delay_count
            jmp menu_check_fire

menu_minus_pressed:
            ; Decrease speed (towards 0.5x)
            lda speed_setting
            beq menu_check_fire     ; Already at min
            dec speed_setting
            jsr play_menu_move
            jsr draw_speed_state
            lda #KEY_DELAY
            sta key_delay_count

menu_check_fire:
            ; Check for fire button
            lda CIA1_PRA
            and #$10
            beq menu_fire_pressed

            ; Check space bar
            lda #$7F
            sta CIA1_PRA
            lda CIA1_PRB
            and #$10
            beq menu_fire_pressed

            lda #$FF
            sta CIA1_PRA
            rts

menu_fire_pressed:
            lda #$FF
            sta CIA1_PRA

            ; Play select sound
            jsr play_menu_select

            ; Store selected song
            lda cursor_pos
            sta selected_song

            ; Set difficulty parameters
            jsr set_difficulty_params

            ; Transition to game
            jsr transition_to_game
            rts

; ----------------------------------------------------------------------------
; Set Difficulty Parameters
; ----------------------------------------------------------------------------

set_difficulty_params:
            lda difficulty
            cmp #DIFF_EASY
            beq set_diff_easy
            cmp #DIFF_NORMAL
            beq set_diff_normal
            jmp set_diff_hard

set_diff_easy:
            lda #PERFECT_WINDOW_EASY
            sta perfect_window
            lda #GOOD_WINDOW_EASY
            sta good_window
            lda #HEALTH_MISS_EASY
            sta health_miss_amt
            rts

set_diff_normal:
            lda #PERFECT_WINDOW_NORMAL
            sta perfect_window
            lda #GOOD_WINDOW_NORMAL
            sta good_window
            lda #HEALTH_MISS_NORMAL
            sta health_miss_amt
            rts

set_diff_hard:
            lda #PERFECT_WINDOW_HARD
            sta perfect_window
            lda #GOOD_WINDOW_HARD
            sta good_window
            lda #HEALTH_MISS_HARD
            sta health_miss_amt
            rts

; ----------------------------------------------------------------------------
; Transition to Game
; ----------------------------------------------------------------------------

transition_to_game:
            ; Set up tempo based on song selection
            lda selected_song
            beq setup_song1
            cmp #1
            beq setup_song2
            cmp #2
            bne +
            jmp setup_song3
+           cmp #3
            bne ++
            jmp setup_song4
++          cmp #4
            bne +++
            jmp setup_song5
+++         jmp setup_song6

setup_song1:
            lda #TEMPO_SONG1
            sta frames_per_beat
            lda #LENGTH_SONG1
            sta song_length

            ; Select difficulty variant for Song 1
            lda difficulty
            cmp #DIFF_EASY
            beq load_s1_easy
            cmp #DIFF_NORMAL
            beq load_s1_normal
            jmp load_s1_hard

load_s1_easy:
            lda #<song1_easy
            sta song_data_ptr
            lda #>song1_easy
            sta song_data_ptr+1
            jmp start_game

load_s1_normal:
            lda #<song1_normal
            sta song_data_ptr
            lda #>song1_normal
            sta song_data_ptr+1
            jmp start_game

load_s1_hard:
            lda #<song1_hard
            sta song_data_ptr
            lda #>song1_hard
            sta song_data_ptr+1
            jmp start_game

setup_song2:
            lda #TEMPO_SONG2
            sta frames_per_beat
            lda #LENGTH_SONG2
            sta song_length

            ; Select difficulty variant for Song 2
            lda difficulty
            cmp #DIFF_EASY
            beq load_s2_easy
            cmp #DIFF_NORMAL
            beq load_s2_normal
            jmp load_s2_hard

load_s2_easy:
            lda #<song2_easy
            sta song_data_ptr
            lda #>song2_easy
            sta song_data_ptr+1
            jmp start_game

load_s2_normal:
            lda #<song2_normal
            sta song_data_ptr
            lda #>song2_normal
            sta song_data_ptr+1
            jmp start_game

load_s2_hard:
            lda #<song2_hard
            sta song_data_ptr
            lda #>song2_hard
            sta song_data_ptr+1
            jmp start_game

setup_song3:
            lda #TEMPO_SONG3
            sta frames_per_beat
            lda #LENGTH_SONG3
            sta song_length

            ; Select difficulty variant for Song 3
            lda difficulty
            cmp #DIFF_EASY
            beq load_s3_easy
            cmp #DIFF_NORMAL
            beq load_s3_normal
            jmp load_s3_hard

load_s3_easy:
            lda #<song3_easy
            sta song_data_ptr
            lda #>song3_easy
            sta song_data_ptr+1
            jmp start_game

load_s3_normal:
            lda #<song3_normal
            sta song_data_ptr
            lda #>song3_normal
            sta song_data_ptr+1
            jmp start_game

load_s3_hard:
            lda #<song3_hard
            sta song_data_ptr
            lda #>song3_hard
            sta song_data_ptr+1
            jmp start_game

setup_song4:
            lda #TEMPO_SONG4
            sta frames_per_beat
            lda #LENGTH_SONG4
            sta song_length

            ; Select difficulty variant for Song 4
            lda difficulty
            cmp #DIFF_EASY
            beq load_s4_easy
            cmp #DIFF_NORMAL
            beq load_s4_normal
            jmp load_s4_hard

load_s4_easy:
            lda #<song4_easy
            sta song_data_ptr
            lda #>song4_easy
            sta song_data_ptr+1
            jmp start_game

load_s4_normal:
            lda #<song4_normal
            sta song_data_ptr
            lda #>song4_normal
            sta song_data_ptr+1
            jmp start_game

load_s4_hard:
            lda #<song4_hard
            sta song_data_ptr
            lda #>song4_hard
            sta song_data_ptr+1
            jmp start_game

setup_song5:
            lda #TEMPO_SONG5
            sta frames_per_beat
            lda #LENGTH_SONG5
            sta song_length

            ; Select difficulty variant for Song 5
            lda difficulty
            cmp #DIFF_EASY
            beq load_s5_easy
            cmp #DIFF_NORMAL
            beq load_s5_normal
            jmp load_s5_hard

load_s5_easy:
            lda #<song5_easy
            sta song_data_ptr
            lda #>song5_easy
            sta song_data_ptr+1
            jmp start_game

load_s5_normal:
            lda #<song5_normal
            sta song_data_ptr
            lda #>song5_normal
            sta song_data_ptr+1
            jmp start_game

load_s5_hard:
            lda #<song5_hard
            sta song_data_ptr
            lda #>song5_hard
            sta song_data_ptr+1
            jmp start_game

setup_song6:
            lda #TEMPO_SONG6
            sta frames_per_beat
            lda #LENGTH_SONG6
            sta song_length

            ; Select difficulty variant for Song 6
            lda difficulty
            cmp #DIFF_EASY
            beq load_s6_easy
            cmp #DIFF_NORMAL
            beq load_s6_normal
            jmp load_s6_hard

load_s6_easy:
            lda #<song6_easy
            sta song_data_ptr
            lda #>song6_easy
            sta song_data_ptr+1
            jmp start_game

load_s6_normal:
            lda #<song6_normal
            sta song_data_ptr
            lda #>song6_normal
            sta song_data_ptr+1
            jmp start_game

load_s6_hard:
            lda #<song6_hard
            sta song_data_ptr
            lda #>song6_hard
            sta song_data_ptr+1

start_game:
            ; Apply speed modifier to tempo
            jsr apply_speed_modifier

            ; Initialise game
            jsr init_game
            lda #STATE_PLAYING
            sta game_state
            rts

; Apply speed modifier to frames_per_beat
apply_speed_modifier:
            lda speed_setting
            beq speed_half_tempo    ; 0 = 0.5x
            cmp #1
            beq speed_3qrtr_tempo   ; 1 = 0.75x
            rts                     ; 2 = 1.0x, no change

speed_half_tempo:
            ; 0.5x speed = double frames_per_beat
            asl frames_per_beat
            rts

speed_3qrtr_tempo:
            ; 0.75x speed = frames_per_beat + frames_per_beat/3
            ; Approximation: add half (will be roughly 1.5x, close to 0.75x)
            lda frames_per_beat
            lsr                     ; Divide by 2
            clc
            adc frames_per_beat     ; Add to original = 1.5x frames
            sta frames_per_beat
            rts

; Song data pointers
song_data_ptr:
            !word 0

; ----------------------------------------------------------------------------
; Play Menu Move Sound
; ----------------------------------------------------------------------------

play_menu_move:
            lda #0
            sta SID_V3_FREQ_LO
            lda #MENU_MOVE_FREQ
            sta SID_V3_FREQ_HI
            lda #$08
            sta SID_V3_PWHI
            lda #MENU_MOVE_AD
            sta SID_V3_AD
            lda #MENU_MOVE_SR
            sta SID_V3_SR
            lda #MENU_MOVE_WAVE
            ora #$01            ; Gate on
            sta SID_V3_CTRL
            rts

; ----------------------------------------------------------------------------
; Play Menu Select Sound
; ----------------------------------------------------------------------------

play_menu_select:
            lda #0
            sta SID_V3_FREQ_LO
            lda #MENU_SELECT_FREQ
            sta SID_V3_FREQ_HI
            lda #$08
            sta SID_V3_PWHI
            lda #MENU_SELECT_AD
            sta SID_V3_AD
            lda #MENU_SELECT_SR
            sta SID_V3_SR
            lda #MENU_SELECT_WAVE
            ora #$01            ; Gate on
            sta SID_V3_CTRL
            rts

; ----------------------------------------------------------------------------
; Initialize Game
; ----------------------------------------------------------------------------

init_game:
            jsr init_screen
            jsr init_notes
            jsr init_score
            jsr init_health
            jsr init_combo
            jsr init_song

            ; Initialize visual effect variables
            lda #0
            sta bg_flash
            sta beat_pulse

            ; Initialize advanced scoring variables
            sta perfect_streak
            sta best_streak
            sta section_bonus
            sta notes_hit

            ; Initialize endless mode loop counter
            lda #1                  ; Start at loop 1
            sta loop_count

            ; Display loop count if in endless mode
            lda endless_mode
            beq init_game_done
            jsr display_loop_count

init_game_done:
            rts

; ----------------------------------------------------------------------------
; Initialize Combo
; ----------------------------------------------------------------------------

init_combo:
            lda #0
            sta combo
            sta max_combo
            jsr display_combo
            rts

; ----------------------------------------------------------------------------
; Update Playing State
; ----------------------------------------------------------------------------

update_playing:
            inc frame_count
            lda frame_count
            cmp frames_per_beat
            bcc no_new_beat

            lda #0
            sta frame_count
            jsr advance_song
            jsr check_section_bonus ; Check for section completion bonus
            jsr check_spawn_note

            ; Trigger beat pulse on every new beat
            lda #8                  ; Pulse duration (frames)
            sta beat_pulse

no_new_beat:
            jsr update_notes
            jsr reset_track_colours
            jsr update_border_flash
            jsr check_keys
            jsr check_song_end

            rts

; ----------------------------------------------------------------------------
; Update Results State
; ----------------------------------------------------------------------------

update_results:
            ; Check for fire to return to menu
            lda CIA1_PRA
            and #$10
            beq results_fire

            ; Also check space bar
            lda #$7F
            sta CIA1_PRA
            lda CIA1_PRB
            and #$10
            beq results_fire

            lda #$FF
            sta CIA1_PRA
            rts

results_fire:
            lda #$FF
            sta CIA1_PRA

            jsr play_menu_select

            ; Return to menu
            jsr show_menu
            lda #STATE_MENU
            sta game_state
            rts

; ----------------------------------------------------------------------------
; Update Game Over State
; ----------------------------------------------------------------------------

update_gameover:
            ; Check for fire to return to menu
            lda CIA1_PRA
            and #$10
            beq gameover_fire

            ; Also check space bar
            lda #$7F
            sta CIA1_PRA
            lda CIA1_PRB
            and #$10
            beq gameover_fire

            lda #$FF
            sta CIA1_PRA
            rts

gameover_fire:
            lda #$FF
            sta CIA1_PRA

            jsr play_menu_select

            ; Return to menu
            jsr show_menu
            lda #STATE_MENU
            sta game_state
            rts

; ----------------------------------------------------------------------------
; Initialize Screen (Gameplay)
; ----------------------------------------------------------------------------

init_screen:
            ; Enable custom charset
            lda #$1C            ; Character ROM at $3000
            sta CHARPTR

            ; Set colours
            lda #BORDER_COL
            sta BORDER
            lda #BG_COL
            sta BGCOL

            ; Clear screen
            ldx #0
            lda #CHAR_SPACE
clear_screen:
            sta SCREEN,x
            sta SCREEN+$100,x
            sta SCREEN+$200,x
            sta SCREEN+$2E8,x
            inx
            bne clear_screen

            ; Draw tracks
            ldx #0
draw_tracks:
            lda #CHAR_TRACK
            sta SCREEN + (TRACK1_ROW * 40),x
            sta SCREEN + (TRACK2_ROW * 40),x
            sta SCREEN + (TRACK3_ROW * 40),x
            lda #TRACK_LINE_COL
            sta COLRAM + (TRACK1_ROW * 40),x
            sta COLRAM + (TRACK2_ROW * 40),x
            sta COLRAM + (TRACK3_ROW * 40),x
            inx
            cpx #38
            bne draw_tracks

            ; Draw hit zones
            lda #CHAR_HITZONE
            sta SCREEN + (TRACK1_ROW * 40) + HIT_ZONE_COLUMN
            sta SCREEN + (TRACK2_ROW * 40) + HIT_ZONE_COLUMN
            sta SCREEN + (TRACK3_ROW * 40) + HIT_ZONE_COLUMN
            lda #HIT_ZONE_COL
            sta COLRAM + (TRACK1_ROW * 40) + HIT_ZONE_COLUMN
            sta COLRAM + (TRACK2_ROW * 40) + HIT_ZONE_COLUMN
            sta COLRAM + (TRACK3_ROW * 40) + HIT_ZONE_COLUMN

            ; Draw track labels
            lda #$31            ; "1"
            sta SCREEN + (TRACK1_ROW * 40)
            lda #$32            ; "2"
            sta SCREEN + (TRACK2_ROW * 40)
            lda #$33            ; "3"
            sta SCREEN + (TRACK3_ROW * 40)
            lda #1
            sta COLRAM + (TRACK1_ROW * 40)
            sta COLRAM + (TRACK2_ROW * 40)
            sta COLRAM + (TRACK3_ROW * 40)

            ; Draw HUD labels
            ldx #0
draw_score_label:
            lda score_label,x
            beq draw_miss_label_start
            sta SCREEN,x
            lda #7
            sta COLRAM,x
            inx
            jmp draw_score_label

draw_miss_label_start:
            ldx #0
draw_miss_label:
            lda miss_label,x
            beq draw_combo_label_start
            sta SCREEN + 14,x
            lda #2
            sta COLRAM + 14,x
            inx
            jmp draw_miss_label

draw_combo_label_start:
            ldx #0
draw_combo_label:
            lda combo_label,x
            beq draw_health_label_start
            sta SCREEN + (COMBO_ROW * 40) + 27,x
            lda #COMBO_COL
            sta COLRAM + (COMBO_ROW * 40) + 27,x
            inx
            jmp draw_combo_label

draw_health_label_start:
            ldx #0
draw_health_label:
            lda health_label,x
            beq draw_progress_label_start
            sta SCREEN + (HEALTH_ROW * 40),x
            lda #HEALTH_COL
            sta COLRAM + (HEALTH_ROW * 40),x
            inx
            jmp draw_health_label

draw_progress_label_start:
            ldx #0
draw_progress_label:
            lda progress_label,x
            beq labels_done
            sta SCREEN + (PROGRESS_ROW * 40),x
            lda #PROGRESS_COL
            sta COLRAM + (PROGRESS_ROW * 40),x
            inx
            jmp draw_progress_label

labels_done:
            ; Draw song name based on selection
            lda selected_song
            beq draw_name_song1
            jmp draw_name_song2

draw_name_song1:
            ldx #0
draw_sn1:
            lda song1_name,x
            beq draw_difficulty_hud
            sta SCREEN + 28,x
            lda #11
            sta COLRAM + 28,x
            inx
            jmp draw_sn1

draw_name_song2:
            ldx #0
draw_sn2:
            lda song2_name,x
            beq draw_difficulty_hud
            sta SCREEN + 26,x
            lda #11
            sta COLRAM + 26,x
            inx
            jmp draw_sn2

draw_difficulty_hud:
            ; Show difficulty on gameplay HUD
            lda difficulty
            cmp #DIFF_EASY
            beq draw_hud_easy
            cmp #DIFF_NORMAL
            beq draw_hud_normal
            jmp draw_hud_hard

draw_hud_easy:
            ldx #0
draw_hud_e:
            lda easy_text,x
            beq draw_song_name_done
            sta SCREEN + (COMBO_ROW * 40),x
            lda #EASY_COL
            sta COLRAM + (COMBO_ROW * 40),x
            inx
            jmp draw_hud_e

draw_hud_normal:
            ldx #0
draw_hud_n:
            lda normal_text,x
            beq draw_song_name_done
            sta SCREEN + (COMBO_ROW * 40),x
            lda #NORMAL_COL
            sta COLRAM + (COMBO_ROW * 40),x
            inx
            jmp draw_hud_n

draw_hud_hard:
            ldx #0
draw_hud_h:
            lda hard_text,x
            beq draw_song_name_done
            sta SCREEN + (COMBO_ROW * 40),x
            lda #HARD_COL
            sta COLRAM + (COMBO_ROW * 40),x
            inx
            jmp draw_hud_h

draw_song_name_done:
            rts

score_label:
            !scr "score:"
            !byte 0
miss_label:
            !scr "miss:"
            !byte 0
combo_label:
            !scr "combo:"
            !byte 0
health_label:
            !scr "health:"
            !byte 0
progress_label:
            !scr "song:"
            !byte 0

; ----------------------------------------------------------------------------
; Initialize Notes
; ----------------------------------------------------------------------------

init_notes:
            ldx #0
            lda #0
clear_notes:
            sta note_track,x
            sta note_col,x
            sta note_freq,x
            inx
            cpx #MAX_NOTES
            bne clear_notes
            rts

; ----------------------------------------------------------------------------
; Initialize Score
; ----------------------------------------------------------------------------

init_score:
            lda #0
            sta score_lo
            sta score_hi
            sta miss_count
            sta perfect_count
            sta good_count
            jsr display_score
            jsr display_misses
            rts

; ----------------------------------------------------------------------------
; Initialize Health
; ----------------------------------------------------------------------------

init_health:
            lda #HEALTH_START
            sta health
            jsr display_health
            rts

; ----------------------------------------------------------------------------
; Initialize Song
; ----------------------------------------------------------------------------

init_song:
            lda #0
            sta frame_count
            sta beat_count
            sta song_beat
            sta song_ended

            ; Point to selected song data
            lda song_data_ptr
            sta song_pos
            lda song_data_ptr+1
            sta song_pos_hi

            jsr display_progress
            rts

; ----------------------------------------------------------------------------
; Initialize SID
; ----------------------------------------------------------------------------

init_sid:
            ; Clear SID
            ldx #$18
            lda #0
clear_sid:
            sta SID,x
            dex
            bpl clear_sid

            ; Set volume
            lda #$0F
            sta SID_VOLUME

            ; Set pulse widths
            lda #PULSE_WIDTH
            sta SID_V1_PWHI
            sta SID_V2_PWHI
            sta SID_V3_PWHI

            ; Set ADSR for all voices
            lda #VOICE_AD
            sta SID_V1_AD
            sta SID_V2_AD
            sta SID_V3_AD

            lda #VOICE_SR
            sta SID_V1_SR
            sta SID_V2_SR
            sta SID_V3_SR

            rts

; ----------------------------------------------------------------------------
; Copy Custom Charset
; ----------------------------------------------------------------------------

copy_charset:
            ; Copy default ROM charset to RAM first
            sei
            lda #$33
            sta $01

            ldx #0
copy_rom_chars:
            lda $D000,x
            sta CHARSET,x
            lda $D100,x
            sta CHARSET+$100,x
            lda $D200,x
            sta CHARSET+$200,x
            lda $D300,x
            sta CHARSET+$300,x
            lda $D400,x
            sta CHARSET+$400,x
            lda $D500,x
            sta CHARSET+$500,x
            lda $D600,x
            sta CHARSET+$600,x
            lda $D700,x
            sta CHARSET+$700,x
            inx
            bne copy_rom_chars

            lda #$37
            sta $01
            cli

            ; Now add custom characters

            ; Character 128 - Note (solid circle)
            lda #%00111100
            sta CHARSET + (128 * 8) + 0
            lda #%01111110
            sta CHARSET + (128 * 8) + 1
            lda #%11111111
            sta CHARSET + (128 * 8) + 2
            lda #%11111111
            sta CHARSET + (128 * 8) + 3
            lda #%11111111
            sta CHARSET + (128 * 8) + 4
            lda #%11111111
            sta CHARSET + (128 * 8) + 5
            lda #%01111110
            sta CHARSET + (128 * 8) + 6
            lda #%00111100
            sta CHARSET + (128 * 8) + 7

            ; Character 129 - Track line (dashes)
            lda #%00000000
            sta CHARSET + (129 * 8) + 0
            lda #%00000000
            sta CHARSET + (129 * 8) + 1
            lda #%00000000
            sta CHARSET + (129 * 8) + 2
            lda #%11001100
            sta CHARSET + (129 * 8) + 3
            lda #%11001100
            sta CHARSET + (129 * 8) + 4
            lda #%00000000
            sta CHARSET + (129 * 8) + 5
            lda #%00000000
            sta CHARSET + (129 * 8) + 6
            lda #%00000000
            sta CHARSET + (129 * 8) + 7

            ; Character 130 - Hit zone (vertical bars)
            lda #%10000001
            sta CHARSET + (130 * 8) + 0
            lda #%10000001
            sta CHARSET + (130 * 8) + 1
            lda #%10000001
            sta CHARSET + (130 * 8) + 2
            lda #%10000001
            sta CHARSET + (130 * 8) + 3
            lda #%10000001
            sta CHARSET + (130 * 8) + 4
            lda #%10000001
            sta CHARSET + (130 * 8) + 5
            lda #%10000001
            sta CHARSET + (130 * 8) + 6
            lda #%10000001
            sta CHARSET + (130 * 8) + 7

            ; Character 131 - Bar full (solid block)
            lda #%11111111
            sta CHARSET + (131 * 8) + 0
            sta CHARSET + (131 * 8) + 1
            sta CHARSET + (131 * 8) + 2
            sta CHARSET + (131 * 8) + 3
            sta CHARSET + (131 * 8) + 4
            sta CHARSET + (131 * 8) + 5
            sta CHARSET + (131 * 8) + 6
            sta CHARSET + (131 * 8) + 7

            ; Character 132 - Bar empty (hollow block)
            lda #%11111111
            sta CHARSET + (132 * 8) + 0
            lda #%10000001
            sta CHARSET + (132 * 8) + 1
            sta CHARSET + (132 * 8) + 2
            sta CHARSET + (132 * 8) + 3
            sta CHARSET + (132 * 8) + 4
            sta CHARSET + (132 * 8) + 5
            sta CHARSET + (132 * 8) + 6
            lda #%11111111
            sta CHARSET + (132 * 8) + 7

            rts

; ----------------------------------------------------------------------------
; Advance Song
; ----------------------------------------------------------------------------

advance_song:
            inc song_beat
            jsr display_progress
            rts

; ----------------------------------------------------------------------------
; Check Spawn Note
; ----------------------------------------------------------------------------

check_spawn_note:
            ; Check if song has ended
            lda song_ended
            bne spawn_done

            ; Get pointer to current position in song
            lda song_pos
            sta ZP_PTR
            lda song_pos_hi
            sta ZP_PTR_HI

            ; Read beat number
            ldy #0
            lda (ZP_PTR),y

            ; Check for end marker
            cmp #$FF
            beq song_end_marker

            ; Check if this beat matches
            cmp song_beat
            bne spawn_done

            ; Spawn this note
            iny
            lda (ZP_PTR),y      ; Track
            sta temp_track
            iny
            lda (ZP_PTR),y      ; Frequency

            ; Find free note slot
            ldx #0
find_slot:
            lda note_track,x
            beq found_slot
            inx
            cpx #MAX_NOTES
            bne find_slot
            jmp advance_song_ptr  ; No free slot, skip note

found_slot:
            lda temp_track
            sta note_track,x
            lda #NOTE_SPAWN_COL
            sta note_col,x
            ldy #2
            lda (ZP_PTR),y
            sta note_freq,x
            jsr draw_note

advance_song_ptr:
            ; Move to next note in song
            lda song_pos
            clc
            adc #3
            sta song_pos
            lda song_pos_hi
            adc #0
            sta song_pos_hi

            ; Check if there are more notes on this beat
            jmp check_spawn_note

song_end_marker:
            lda #1
            sta song_ended
spawn_done:
            rts

; ----------------------------------------------------------------------------
; Update Notes
; ----------------------------------------------------------------------------

update_notes:
            ldx #0
update_notes_loop:
            lda note_track,x
            beq update_next_note

            ; Erase at old position
            jsr erase_note

            ; Move note left
            dec note_col,x

            ; Check if past hit zone (missed)
            lda note_col,x
            cmp #HIT_ZONE_START
            bcs note_still_active

            ; Note was missed
            lda note_track,x
            sta miss_track
            lda #0
            sta note_track,x
            jsr handle_miss
            jmp update_next_note

note_still_active:
            jsr draw_note

update_next_note:
            inx
            cpx #MAX_NOTES
            bne update_notes_loop
            rts

; ----------------------------------------------------------------------------
; Draw Note
; ----------------------------------------------------------------------------

draw_note:
            lda note_track,x
            cmp #1
            beq draw_note_t1
            cmp #2
            beq draw_note_t2
            jmp draw_note_t3

draw_note_t1:
            lda note_col,x
            clc
            adc #<(SCREEN + TRACK1_ROW * 40)
            sta ZP_PTR
            lda #>(SCREEN + TRACK1_ROW * 40)
            adc #0
            sta ZP_PTR_HI

            lda #CHAR_NOTE
            ldy #0
            sta (ZP_PTR),y

            ; Set colour
            lda note_col,x
            clc
            adc #<(COLRAM + TRACK1_ROW * 40)
            sta ZP_PTR
            lda #>(COLRAM + TRACK1_ROW * 40)
            adc #0
            sta ZP_PTR_HI
            lda #TRACK1_NOTE_COL
            sta (ZP_PTR),y
            rts

draw_note_t2:
            lda note_col,x
            clc
            adc #<(SCREEN + TRACK2_ROW * 40)
            sta ZP_PTR
            lda #>(SCREEN + TRACK2_ROW * 40)
            adc #0
            sta ZP_PTR_HI

            lda #CHAR_NOTE
            ldy #0
            sta (ZP_PTR),y

            lda note_col,x
            clc
            adc #<(COLRAM + TRACK2_ROW * 40)
            sta ZP_PTR
            lda #>(COLRAM + TRACK2_ROW * 40)
            adc #0
            sta ZP_PTR_HI
            lda #TRACK2_NOTE_COL
            sta (ZP_PTR),y
            rts

draw_note_t3:
            lda note_col,x
            clc
            adc #<(SCREEN + TRACK3_ROW * 40)
            sta ZP_PTR
            lda #>(SCREEN + TRACK3_ROW * 40)
            adc #0
            sta ZP_PTR_HI

            lda #CHAR_NOTE
            ldy #0
            sta (ZP_PTR),y

            lda note_col,x
            clc
            adc #<(COLRAM + TRACK3_ROW * 40)
            sta ZP_PTR
            lda #>(COLRAM + TRACK3_ROW * 40)
            adc #0
            sta ZP_PTR_HI
            lda #TRACK3_NOTE_COL
            sta (ZP_PTR),y
            rts

; ----------------------------------------------------------------------------
; Erase Note
; ----------------------------------------------------------------------------

erase_note:
            lda note_track,x
            cmp #1
            beq erase_note_t1
            cmp #2
            beq erase_note_t2
            jmp erase_note_t3

erase_note_t1:
            lda note_col,x
            clc
            adc #<(SCREEN + TRACK1_ROW * 40)
            sta ZP_PTR
            lda #>(SCREEN + TRACK1_ROW * 40)
            adc #0
            sta ZP_PTR_HI

            lda #CHAR_TRACK
            ldy #0
            sta (ZP_PTR),y

            lda note_col,x
            clc
            adc #<(COLRAM + TRACK1_ROW * 40)
            sta ZP_PTR
            lda #>(COLRAM + TRACK1_ROW * 40)
            adc #0
            sta ZP_PTR_HI
            lda #TRACK_LINE_COL
            sta (ZP_PTR),y
            rts

erase_note_t2:
            lda note_col,x
            clc
            adc #<(SCREEN + TRACK2_ROW * 40)
            sta ZP_PTR
            lda #>(SCREEN + TRACK2_ROW * 40)
            adc #0
            sta ZP_PTR_HI

            lda #CHAR_TRACK
            ldy #0
            sta (ZP_PTR),y

            lda note_col,x
            clc
            adc #<(COLRAM + TRACK2_ROW * 40)
            sta ZP_PTR
            lda #>(COLRAM + TRACK2_ROW * 40)
            adc #0
            sta ZP_PTR_HI
            lda #TRACK_LINE_COL
            sta (ZP_PTR),y
            rts

erase_note_t3:
            lda note_col,x
            clc
            adc #<(SCREEN + TRACK3_ROW * 40)
            sta ZP_PTR
            lda #>(SCREEN + TRACK3_ROW * 40)
            adc #0
            sta ZP_PTR_HI

            lda #CHAR_TRACK
            ldy #0
            sta (ZP_PTR),y

            lda note_col,x
            clc
            adc #<(COLRAM + TRACK3_ROW * 40)
            sta ZP_PTR
            lda #>(COLRAM + TRACK3_ROW * 40)
            adc #0
            sta ZP_PTR_HI
            lda #TRACK_LINE_COL
            sta (ZP_PTR),y
            rts

; ----------------------------------------------------------------------------
; Handle Miss
; ----------------------------------------------------------------------------

handle_miss:
            jsr play_miss_sound
            jsr break_combo

            ; In practice mode, don't decrease health
            lda practice_mode
            bne skip_health_loss

            ; Decrease health (using difficulty-adjusted amount)
            lda health_miss_amt
            jsr decrease_health

skip_health_loss:
            ; Flash border red
            lda #MISS_COL
            sta BORDER
            lda #8
            sta border_flash

            ; Flash the track where miss happened
            lda miss_track
            cmp #1
            beq flash_miss_t1
            cmp #2
            beq flash_miss_t2
            jmp flash_miss_t3

flash_miss_t1:
            ldx #0
            lda #MISS_COL
flash_m1_loop:
            sta COLRAM + (TRACK1_ROW * 40),x
            inx
            cpx #38
            bne flash_m1_loop
            rts

flash_miss_t2:
            ldx #0
            lda #MISS_COL
flash_m2_loop:
            sta COLRAM + (TRACK2_ROW * 40),x
            inx
            cpx #38
            bne flash_m2_loop
            rts

flash_miss_t3:
            ldx #0
            lda #MISS_COL
flash_m3_loop:
            sta COLRAM + (TRACK3_ROW * 40),x
            inx
            cpx #38
            bne flash_m3_loop
            rts

; ----------------------------------------------------------------------------
; Play Miss Sound
; ----------------------------------------------------------------------------

play_miss_sound:
            inc miss_count
            jsr display_misses

            lda #0
            sta SID_V3_FREQ_LO
            lda #MISS_FREQ
            sta SID_V3_FREQ_HI
            lda #MISS_AD
            sta SID_V3_AD
            lda #MISS_SR
            sta SID_V3_SR
            lda #MISS_WAVE
            ora #$01
            sta SID_V3_CTRL
            rts

; ----------------------------------------------------------------------------
; Play Perfect Sound
; ----------------------------------------------------------------------------

play_perfect_sound:
            lda #0
            sta SID_V3_FREQ_LO
            lda #PERFECT_SFX_FREQ
            sta SID_V3_FREQ_HI
            lda #PERFECT_SFX_AD
            sta SID_V3_AD
            lda #PERFECT_SFX_SR
            sta SID_V3_SR
            lda #PERFECT_SFX_WAVE
            ora #$01
            sta SID_V3_CTRL
            rts

; ----------------------------------------------------------------------------
; Play Good Sound
; ----------------------------------------------------------------------------

play_good_sound:
            lda #0
            sta SID_V3_FREQ_LO
            lda #GOOD_SFX_FREQ
            sta SID_V3_FREQ_HI
            lda #GOOD_SFX_AD
            sta SID_V3_AD
            lda #GOOD_SFX_SR
            sta SID_V3_SR
            lda #GOOD_SFX_WAVE
            ora #$01
            sta SID_V3_CTRL
            rts

; ----------------------------------------------------------------------------
; Increase Health
; ----------------------------------------------------------------------------

increase_health:
            clc
            adc health
            cmp #HEALTH_MAX
            bcc health_ok
            lda #HEALTH_MAX
health_ok:
            sta health
            jsr display_health
            rts

; ----------------------------------------------------------------------------
; Decrease Health
; ----------------------------------------------------------------------------

decrease_health:
            sta temp_track
            lda health
            sec
            sbc temp_track
            bcs health_not_zero
            lda #0
health_not_zero:
            sta health
            jsr display_health

            ; Check for game over
            lda health
            bne no_game_over
            jsr show_gameover
            lda #STATE_GAMEOVER
            sta game_state
no_game_over:
            rts

; ----------------------------------------------------------------------------
; Display Health Bar
; ----------------------------------------------------------------------------

display_health:
            ; Calculate filled blocks (health / 4 = blocks, max 16)
            lda health
            lsr
            lsr
            sta temp_track

            ldx #0
draw_health_bar:
            cpx temp_track
            bcs draw_empty_health
            lda #CHAR_BAR_FULL
            jmp store_health_char
draw_empty_health:
            lda #CHAR_BAR_EMPTY
store_health_char:
            sta SCREEN + (HEALTH_ROW * 40) + 8,x
            lda #HEALTH_COL
            sta COLRAM + (HEALTH_ROW * 40) + 8,x
            inx
            cpx #16
            bne draw_health_bar
            rts

; ----------------------------------------------------------------------------
; Display Progress Bar
; ----------------------------------------------------------------------------

display_progress:
            ; Calculate progress (song_beat / 4)
            lda song_beat
            lsr
            lsr
            sta temp_track

            ldx #0
draw_progress_bar:
            cpx temp_track
            bcs draw_empty_progress
            lda #CHAR_BAR_FULL
            jmp store_progress_char
draw_empty_progress:
            lda #CHAR_BAR_EMPTY
store_progress_char:
            sta SCREEN + (PROGRESS_ROW * 40) + 8,x
            lda #PROGRESS_COL
            sta COLRAM + (PROGRESS_ROW * 40) + 8,x
            inx
            cpx #PROGRESS_WIDTH
            bne draw_progress_bar
            rts

; ----------------------------------------------------------------------------
; Combo System
; ----------------------------------------------------------------------------

increment_combo:
            inc combo
            lda combo
            cmp max_combo
            bcc combo_no_max
            sta max_combo
combo_no_max:
            jsr display_combo
            rts

break_combo:
            lda #0
            sta combo
            jsr display_combo
            rts

display_combo:
            ; Display combo count
            lda combo
            ldx #0
combo_div_100:
            cmp #100
            bcc combo_done_100
            sec
            sbc #100
            inx
            jmp combo_div_100
combo_done_100:
            pha
            txa
            ora #$30
            sta SCREEN + (COMBO_ROW * 40) + 34

            pla
            ldx #0
combo_div_10:
            cmp #10
            bcc combo_done_10
            sec
            sbc #10
            inx
            jmp combo_div_10
combo_done_10:
            pha
            txa
            ora #$30
            sta SCREEN + (COMBO_ROW * 40) + 35

            pla
            ora #$30
            sta SCREEN + (COMBO_ROW * 40) + 36

            ; Colour based on multiplier
            jsr get_multiplier
            cmp #1
            beq combo_col_1x
            cmp #2
            beq combo_col_2x
            cmp #3
            beq combo_col_3x
            jmp combo_col_4x

combo_col_1x:
            lda #11             ; Grey
            ldx #COMBO_BORDER_1 ; Dark grey border
            jmp set_combo_col
combo_col_2x:
            lda #7              ; Yellow
            ldx #COMBO_BORDER_2 ; Green border
            jmp set_combo_col
combo_col_3x:
            lda #5              ; Green
            ldx #COMBO_BORDER_3 ; Yellow border
            jmp set_combo_col
combo_col_4x:
            lda #1              ; White
            ldx #COMBO_BORDER_4 ; Red border (fire!)

set_combo_col:
            sta COLRAM + (COMBO_ROW * 40) + 34
            sta COLRAM + (COMBO_ROW * 40) + 35
            sta COLRAM + (COMBO_ROW * 40) + 36

            ; Update border colour for visual feedback
            lda combo
            beq reset_border    ; No combo = black border
            stx BORDER          ; Set border to tier colour
            rts

reset_border:
            lda #COMBO_BORDER_0
            sta BORDER
            rts

get_multiplier:
            lda combo
            cmp #COMBO_TIER_4
            bcs mult_4x
            cmp #COMBO_TIER_3
            bcs mult_3x
            cmp #COMBO_TIER_2
            bcs mult_2x
            lda #1
            rts
mult_2x:
            lda #2
            rts
mult_3x:
            lda #3
            rts
mult_4x:
            lda #4
            rts

; ----------------------------------------------------------------------------
; Reset Track Colours
; ----------------------------------------------------------------------------

reset_track_colours:
            ldx #1              ; Start at column 1 (skip label)
reset_col_loop:
            lda #TRACK_LINE_COL
            sta COLRAM + (TRACK1_ROW * 40),x
            sta COLRAM + (TRACK2_ROW * 40),x
            sta COLRAM + (TRACK3_ROW * 40),x
            inx
            cpx #38
            bne reset_col_loop

            ; Restore hit zone colours
            lda #HIT_ZONE_COL
            sta COLRAM + (TRACK1_ROW * 40) + HIT_ZONE_COLUMN
            sta COLRAM + (TRACK2_ROW * 40) + HIT_ZONE_COLUMN
            sta COLRAM + (TRACK3_ROW * 40) + HIT_ZONE_COLUMN

            ; Redraw notes with correct colours
            ldx #0
redraw_loop:
            lda note_track,x
            beq redraw_next
            jsr draw_note
redraw_next:
            inx
            cpx #MAX_NOTES
            bne redraw_loop
            rts

; ----------------------------------------------------------------------------
; Update Border Flash
; ----------------------------------------------------------------------------

update_border_flash:
            lda border_flash
            beq border_flash_done
            dec border_flash
            bne border_flash_done
            lda #BORDER_COL
            sta BORDER
border_flash_done:

            ; Update background flash (perfect hit effect)
            lda bg_flash
            beq bg_flash_done
            dec bg_flash
            bne bg_flash_done
            ; Reset background to default
            lda #6                  ; Blue background
            sta BGCOL
bg_flash_done:

            ; Update beat pulse marker
            jsr update_beat_marker
            rts

; ----------------------------------------------------------------------------
; Update Beat Marker
; ----------------------------------------------------------------------------
; Shows a pulsing indicator that helps players feel the rhythm.
; Pulses bright on downbeat, fades through the beat.

update_beat_marker:
            lda beat_pulse
            beq beat_marker_dim

            ; Pulse is active - show bright marker
            dec beat_pulse
            lda beat_pulse
            lsr                     ; Divide by 2 for colour selection
            and #$03                ; Keep in range 0-3
            tax
            lda beat_colours,x
            sta COLRAM + (HIT_ROW * 40) + 19  ; Centre of hit zone
            lda #$51                ; Filled circle character
            sta SCREEN + (HIT_ROW * 40) + 19
            rts

beat_marker_dim:
            ; Show dim marker when not pulsing
            lda #11                 ; Dark grey
            sta COLRAM + (HIT_ROW * 40) + 19
            lda #$57                ; Empty circle character
            sta SCREEN + (HIT_ROW * 40) + 19
            rts

beat_colours:
            !byte 1                 ; White (brightest)
            !byte 7                 ; Yellow
            !byte 5                 ; Green
            !byte 11                ; Dark grey (dimmest)

; ----------------------------------------------------------------------------
; Check Keys
; ----------------------------------------------------------------------------

check_keys:
            lda #$FD
            sta CIA1_PRA
            lda CIA1_PRB
            and #$10
            bne check_x_key

            lda #1
            sta key_pressed
            jsr check_hit
            bcc check_x_key
            jsr play_voice1_note
            jsr flash_track1_hit
            jsr award_points

check_x_key:
            lda #$FB
            sta CIA1_PRA
            lda CIA1_PRB
            and #$80
            bne check_c_key

            lda #2
            sta key_pressed
            jsr check_hit
            bcc check_c_key
            jsr play_voice2_note
            jsr flash_track2_hit
            jsr award_points

check_c_key:
            lda #$FB
            sta CIA1_PRA
            lda CIA1_PRB
            and #$10
            bne check_keys_done

            lda #3
            sta key_pressed
            jsr check_hit
            bcc check_keys_done
            jsr play_voice3_note
            jsr flash_track3_hit
            jsr award_points

check_keys_done:
            lda #$FF
            sta CIA1_PRA
            rts

; ----------------------------------------------------------------------------
; Check Hit (with difficulty-adjusted timing windows)
; ----------------------------------------------------------------------------

check_hit:
            ldx #0

check_hit_loop:
            lda note_track,x
            beq check_hit_next

            cmp key_pressed
            bne check_hit_next

            ; Check if note is in hittable range
            lda note_col,x
            cmp #HIT_ZONE_START
            bcc check_hit_next
            cmp #HIT_ZONE_END+1
            bcs check_hit_next

            ; Note is hittable - determine quality based on distance from perfect
            lda note_freq,x
            sta hit_note_freq

            ; Calculate distance from perfect column
            lda note_col,x
            sec
            sbc #PERFECT_COL_POS
            bcs dist_positive
            ; Negative distance - make it positive
            eor #$FF
            clc
            adc #1
dist_positive:
            ; A now contains absolute distance from perfect

            ; Check if within perfect window
            cmp perfect_window
            bcc hit_is_perfect
            beq hit_is_perfect

            ; Check if within good window
            cmp good_window
            bcc hit_is_good
            beq hit_is_good

            ; Outside good window but in hit zone - still counts as good
            jmp hit_is_good

hit_is_perfect:
            lda #2
            sta hit_quality
            jmp hit_found

hit_is_good:
            lda #1
            sta hit_quality

hit_found:
            jsr erase_note
            lda #0
            sta note_track,x
            sec
            rts

check_hit_next:
            inx
            cpx #MAX_NOTES
            bne check_hit_loop

            lda #0
            sta hit_quality
            clc
            rts

; ----------------------------------------------------------------------------
; Check Song End
; ----------------------------------------------------------------------------

check_song_end:
            lda song_ended
            beq not_ended

            ; Check if all notes cleared
            ldx #0
check_notes_clear:
            lda note_track,x
            bne not_ended
            inx
            cpx #MAX_NOTES
            bne check_notes_clear

            ; Add delay before results
            inc end_delay
            lda end_delay
            cmp #END_DELAY_FRAMES
            bcc not_ended

            ; Check for endless mode
            lda endless_mode
            beq end_song_normal

            ; Endless mode - loop the song
            inc loop_count
            jsr display_loop_count
            jsr init_song           ; Reset song position
            lda #0
            sta end_delay
            sta song_ended
            rts

end_song_normal:
            ; Show results
            jsr show_results
            lda #STATE_RESULTS
            sta game_state

not_ended:
            rts

; ----------------------------------------------------------------------------
; Display Loop Count (for Endless Mode)
; ----------------------------------------------------------------------------

display_loop_count:
            ; Display "LOOP X" in top right during endless mode
            ; Position: row 1, column 32
            lda #'l'
            sta SCREEN + (1 * 40) + 32
            lda #'o'
            sta SCREEN + (1 * 40) + 33
            sta SCREEN + (1 * 40) + 34
            lda #'p'
            sta SCREEN + (1 * 40) + 35
            lda #CHAR_SPACE
            sta SCREEN + (1 * 40) + 36

            ; Display loop number (max 9 displayed)
            lda loop_count
            cmp #10
            bcc display_loop_digit
            lda #9                  ; Cap display at 9
display_loop_digit:
            clc
            adc #$30                ; Convert to ASCII digit
            sta SCREEN + (1 * 40) + 37

            ; Colour the loop display cyan
            lda #3                  ; Cyan
            sta COLRAM + (1 * 40) + 32
            sta COLRAM + (1 * 40) + 33
            sta COLRAM + (1 * 40) + 34
            sta COLRAM + (1 * 40) + 35
            sta COLRAM + (1 * 40) + 37
            rts

; ----------------------------------------------------------------------------
; Award Points
; ----------------------------------------------------------------------------

award_points:
            jsr increment_combo
            inc notes_hit           ; Track total hits for accuracy

            lda hit_quality
            cmp #2
            beq award_perfect

            ; Good hit - resets perfect streak
            lda #0
            sta perfect_streak

            lda #GOOD_SCORE
            jsr apply_multiplier
            jsr add_score

            inc good_count

            ; Play good sound effect
            jsr play_good_sound

            lda #GOOD_COL
            sta BORDER
            lda #4
            sta border_flash

            lda #HEALTH_GOOD
            jsr increase_health

            jmp award_done

award_perfect:
            ; Perfect hit - increment streak
            inc perfect_streak

            ; Check for streak bonus (every STREAK_THRESHOLD perfects)
            lda perfect_streak
            cmp #STREAK_THRESHOLD
            bne no_streak_bonus
            ; Award streak bonus
            lda #STREAK_BONUS
            jsr add_score
            lda #0
            sta perfect_streak      ; Reset for next milestone
            ; Flash border cyan for streak bonus
            lda #3                  ; Cyan
            sta BORDER
no_streak_bonus:

            ; Update best streak
            lda perfect_streak
            cmp best_streak
            bcc skip_best_update
            sta best_streak
skip_best_update:

            lda #PERFECT_SCORE
            jsr apply_multiplier
            jsr add_score

            inc perfect_count

            ; Play perfect sound effect
            jsr play_perfect_sound

            lda #PERFECT_COL
            sta BORDER
            lda #6
            sta border_flash

            ; Screen flash effect for perfect hits
            lda #0                  ; Black background flash
            sta BGCOL
            lda #3                  ; Flash duration
            sta bg_flash

            lda #HEALTH_PERFECT
            jsr increase_health

award_done:
            jsr display_score
            rts

; ----------------------------------------------------------------------------
; Check Section Bonus
; ----------------------------------------------------------------------------
; Awards bonus at 25%, 50%, 75% of song completion

check_section_bonus:
            ; Calculate section thresholds based on song_length
            ; 25% = song_length / 4
            ; 50% = song_length / 2
            ; 75% = song_length * 3 / 4

            ; Check 25% (song_length / 4)
            lda song_length
            lsr
            lsr                     ; A = song_length / 4
            cmp song_beat
            bne check_50

            ; Award 25% section bonus
            lda section_bonus
            and #$01                ; Check if already awarded
            bne section_done
            lda section_bonus
            ora #$01                ; Mark 25% as awarded
            sta section_bonus
            jsr award_section_bonus
            rts

check_50:
            ; Check 50% (song_length / 2)
            lda song_length
            lsr                     ; A = song_length / 2
            cmp song_beat
            bne check_75

            ; Award 50% section bonus
            lda section_bonus
            and #$02
            bne section_done
            lda section_bonus
            ora #$02
            sta section_bonus
            jsr award_section_bonus
            rts

check_75:
            ; Check 75% (song_length - song_length/4)
            lda song_length
            lsr
            lsr
            sta ZP_TEMP             ; song_length / 4
            lda song_length
            sec
            sbc ZP_TEMP             ; song_length - song_length/4
            cmp song_beat
            bne section_done

            ; Award 75% section bonus
            lda section_bonus
            and #$04
            bne section_done
            lda section_bonus
            ora #$04
            sta section_bonus
            jsr award_section_bonus

section_done:
            rts

award_section_bonus:
            ; Add section bonus to score
            lda #SECTION_BONUS
            jsr add_score
            jsr display_score

            ; Flash border purple for section bonus
            lda #4                  ; Purple
            sta BORDER
            lda #8
            sta border_flash
            rts

; ----------------------------------------------------------------------------
; Apply Multiplier
; ----------------------------------------------------------------------------

apply_multiplier:
            sta base_score
            jsr get_multiplier
            sta current_mult

            lda base_score
            sta score_add_lo
            lda #0
            sta score_add_hi

            lda current_mult
            cmp #1
            beq mult_done_apply

            dec current_mult
mult_add_loop:
            lda score_add_lo
            clc
            adc base_score
            sta score_add_lo
            lda score_add_hi
            adc #0
            sta score_add_hi
            dec current_mult
            bne mult_add_loop

mult_done_apply:
            rts

base_score:    !byte 0
current_mult:  !byte 0
score_add_lo:  !byte 0
score_add_hi:  !byte 0

; ----------------------------------------------------------------------------
; Add Score
; ----------------------------------------------------------------------------

add_score:
            lda score_lo
            clc
            adc score_add_lo
            sta score_lo
            lda score_hi
            adc score_add_hi
            sta score_hi
            rts

; ----------------------------------------------------------------------------
; Check and Update High Score
; ----------------------------------------------------------------------------
; Compares current score against high score for current song/difficulty.
; Sets new_high_score flag if current score is higher and updates table.

check_high_score:
            ; Clear new high score flag
            lda #0
            sta new_high_score

            ; Calculate table index: (selected_song * 3 + difficulty) * 2
            lda selected_song
            asl                     ; × 2
            clc
            adc selected_song       ; × 3
            clc
            adc difficulty          ; + difficulty
            asl                     ; × 2 (for 2-byte entries)
            tax                     ; X = table index

            ; Compare current score with high score
            ; First compare high bytes
            lda score_hi
            cmp high_scores+1,x     ; Compare high byte
            bcc hs_not_higher       ; Current < high, not new record
            bne hs_is_higher        ; Current > high, new record

            ; High bytes equal, compare low bytes
            lda score_lo
            cmp high_scores,x       ; Compare low byte
            bcc hs_not_higher       ; Current < high
            beq hs_not_higher       ; Current = high (not strictly greater)

hs_is_higher:
            ; New high score! Update table
            lda score_lo
            sta high_scores,x
            lda score_hi
            sta high_scores+1,x

            ; Set flag
            lda #1
            sta new_high_score

hs_not_higher:
            rts

; Get high score for current song/difficulty into work_lo/work_hi
get_high_score:
            ; Calculate table index: (selected_song * 3 + difficulty) * 2
            lda selected_song
            asl
            clc
            adc selected_song
            clc
            adc difficulty
            asl
            tax

            lda high_scores,x
            sta work_lo
            lda high_scores+1,x
            sta work_hi
            rts

; ----------------------------------------------------------------------------
; Display Score
; ----------------------------------------------------------------------------

display_score:
            lda score_lo
            sta work_lo
            lda score_hi
            sta work_hi

            ldx #0
div_10000:
            lda work_lo
            sec
            sbc #<10000
            tay
            lda work_hi
            sbc #>10000
            bcc done_10000
            sta work_hi
            sty work_lo
            inx
            jmp div_10000
done_10000:
            txa
            ora #$30
            sta SCREEN + 8

            ldx #0
div_1000:
            lda work_lo
            sec
            sbc #<1000
            tay
            lda work_hi
            sbc #>1000
            bcc done_1000
            sta work_hi
            sty work_lo
            inx
            jmp div_1000
done_1000:
            txa
            ora #$30
            sta SCREEN + 9

            ldx #0
div_100:
            lda work_lo
            sec
            sbc #100
            bcc done_100
            sta work_lo
            inx
            jmp div_100
done_100:
            txa
            ora #$30
            sta SCREEN + 10

            ldx #0
div_10:
            lda work_lo
            sec
            sbc #10
            bcc done_10
            sta work_lo
            inx
            jmp div_10
done_10:
            txa
            ora #$30
            sta SCREEN + 11

            lda work_lo
            ora #$30
            sta SCREEN + 12

            lda #7
            sta COLRAM + 8
            sta COLRAM + 9
            sta COLRAM + 10
            sta COLRAM + 11
            sta COLRAM + 12

            rts

; ----------------------------------------------------------------------------
; Display Misses
; ----------------------------------------------------------------------------

display_misses:
            lda miss_count

            ldx #0
miss_div_10:
            cmp #10
            bcc miss_done_10
            sec
            sbc #10
            inx
            jmp miss_div_10
miss_done_10:
            pha
            txa
            ora #$30
            sta SCREEN + 21
            pla
            ora #$30
            sta SCREEN + 22

            lda #2
            sta COLRAM + 21
            sta COLRAM + 22

            rts

work_lo:    !byte 0
work_hi:    !byte 0

; ----------------------------------------------------------------------------
; Play Voices (for note playback)
; ----------------------------------------------------------------------------

play_voice1_note:
            lda #0
            sta SID_V1_FREQ_LO
            lda hit_note_freq
            sta SID_V1_FREQ_HI
            lda #VOICE1_WAVE
            ora #$01
            sta SID_V1_CTRL
            rts

play_voice2_note:
            lda #0
            sta SID_V2_FREQ_LO
            lda hit_note_freq
            sta SID_V2_FREQ_HI
            lda #VOICE2_WAVE
            ora #$01
            sta SID_V2_CTRL
            rts

play_voice3_note:
            lda #0
            sta SID_V3_FREQ_LO
            lda hit_note_freq
            sta SID_V3_FREQ_HI
            lda #VOICE3_WAVE
            ora #$01
            sta SID_V3_CTRL
            rts

; ----------------------------------------------------------------------------
; Flash Tracks
; ----------------------------------------------------------------------------

flash_track1_hit:
            ldx #0
            lda #HIT_COL
flash_t1h_loop:
            sta COLRAM + (TRACK1_ROW * 40),x
            inx
            cpx #38
            bne flash_t1h_loop
            lda #1
            sta COLRAM + (TRACK1_ROW * 40)
            rts

flash_track2_hit:
            ldx #0
            lda #HIT_COL
flash_t2h_loop:
            sta COLRAM + (TRACK2_ROW * 40),x
            inx
            cpx #38
            bne flash_t2h_loop
            lda #1
            sta COLRAM + (TRACK2_ROW * 40)
            rts

flash_track3_hit:
            ldx #0
            lda #HIT_COL
flash_t3h_loop:
            sta COLRAM + (TRACK3_ROW * 40),x
            inx
            cpx #38
            bne flash_t3h_loop
            lda #1
            sta COLRAM + (TRACK3_ROW * 40)
            rts

; ----------------------------------------------------------------------------
; Calculate Performance Grade
; ----------------------------------------------------------------------------
; Calculates grade based on perfect hit percentage
; Sets current_grade (screen code) and current_grade_col (colour)

calculate_grade:
            ; Calculate perfect percentage: (perfect_count * 100) / total_notes
            ; For simplicity, use perfect_count vs notes_hit ratio
            ; notes_hit = perfect_count + good_count
            ; We want: perfect_count / notes_hit * 100

            ; If no notes hit, give D grade
            lda notes_hit
            bne calc_grade_continue
            lda #'d'                ; Screen code for D
            sta current_grade
            lda #2                  ; Red
            sta current_grade_col
            rts

calc_grade_continue:
            ; Calculate percentage: perfect_count * 100 / notes_hit
            ; Use 8-bit approximation for simplicity

            ; First multiply perfect_count by 100
            lda perfect_count
            sta work_lo
            lda #0
            sta work_hi

            ; Multiply by 100 (64 + 32 + 4 = 100)
            ; work = perfect * 64
            asl work_lo
            rol work_hi             ; *2
            asl work_lo
            rol work_hi             ; *4
            asl work_lo
            rol work_hi             ; *8
            asl work_lo
            rol work_hi             ; *16
            asl work_lo
            rol work_hi             ; *32
            asl work_lo
            rol work_hi             ; *64

            ; Save *64 result
            lda work_lo
            sta ZP_TEMP
            lda work_hi
            pha                     ; Save high byte of *64

            ; Get *32 (shift right from *64)
            lsr work_hi
            ror work_lo             ; *32 in work

            ; Add *64 + *32 = *96
            lda ZP_TEMP
            clc
            adc work_lo
            sta work_lo
            pla
            adc work_hi
            sta work_hi             ; work = perfect * 96

            ; Add *4 to get *100
            lda perfect_count
            asl
            asl                     ; A = perfect * 4
            clc
            adc work_lo
            sta work_lo
            lda #0
            adc work_hi
            sta work_hi             ; work = perfect * 100

            ; Now divide by notes_hit
            ; Simple repeated subtraction for small values
            lda #0
            sta ZP_TEMP             ; Quotient (percentage)

grade_div_loop:
            ; Check if work >= notes_hit
            lda work_hi
            bne grade_can_sub       ; High byte nonzero, can subtract
            lda work_lo
            cmp notes_hit
            bcc grade_div_done      ; work < notes_hit, done
grade_can_sub:
            ; Subtract notes_hit from work
            lda work_lo
            sec
            sbc notes_hit
            sta work_lo
            lda work_hi
            sbc #0
            sta work_hi
            inc ZP_TEMP             ; Increment percentage
            lda ZP_TEMP
            cmp #101                ; Cap at 100%
            bcc grade_div_loop
grade_div_done:
            ; ZP_TEMP now contains percentage (0-100)
            ; Determine grade based on thresholds
            lda ZP_TEMP
            cmp #GRADE_S_THRESHOLD
            bcs grade_is_s
            cmp #GRADE_A_THRESHOLD
            bcs grade_is_a
            cmp #GRADE_B_THRESHOLD
            bcs grade_is_b
            cmp #GRADE_C_THRESHOLD
            bcs grade_is_c
            ; Below C threshold = D
            lda #'d'
            sta current_grade
            lda #2                  ; Red
            sta current_grade_col
            rts

grade_is_s:
            lda #'s'
            sta current_grade
            lda #7                  ; Yellow
            sta current_grade_col
            rts

grade_is_a:
            lda #'a'
            sta current_grade
            lda #5                  ; Green
            sta current_grade_col
            rts

grade_is_b:
            lda #'b'
            sta current_grade
            lda #3                  ; Cyan
            sta current_grade_col
            rts

grade_is_c:
            lda #'c'
            sta current_grade
            lda #15                 ; Light grey
            sta current_grade_col
            rts

; ----------------------------------------------------------------------------
; Show Results Screen
; ----------------------------------------------------------------------------

show_results:
            ; Check for full combo bonus (no misses)
            lda miss_count
            bne skip_full_combo
            ; Award full combo bonus!
            lda #FULL_COMBO_BONUS
            jsr add_score
skip_full_combo:

            ; Check if we beat the high score
            jsr check_high_score

            ; Clear screen
            ldx #0
            lda #CHAR_SPACE
clear_results:
            sta SCREEN,x
            sta SCREEN+$100,x
            sta SCREEN+$200,x
            sta SCREEN+$2E8,x
            inx
            bne clear_results

            ; Draw "SONG COMPLETE!"
            ldx #0
draw_complete:
            lda complete_text,x
            beq draw_grade
            sta SCREEN + (5 * 40) + 13,x
            lda #5
            sta COLRAM + (5 * 40) + 13,x
            inx
            jmp draw_complete

draw_grade:
            ; Calculate and display performance grade
            jsr calculate_grade
            ; Display grade label
            ldx #0
draw_grade_label:
            lda grade_label,x
            beq draw_grade_letter
            sta SCREEN + (7 * 40) + 14,x
            lda #7
            sta COLRAM + (7 * 40) + 14,x
            inx
            jmp draw_grade_label

draw_grade_letter:
            ; Grade letter is in A register from calculate_grade
            lda current_grade
            sta SCREEN + (7 * 40) + 22
            ; Colour based on grade
            ldx current_grade_col
            stx COLRAM + (7 * 40) + 22

draw_results_score:
            ; Draw score label
            ldx #0
draw_rs_label:
            lda results_score_label,x
            beq draw_rs_value
            sta SCREEN + (9 * 40) + 12,x
            lda #7
            sta COLRAM + (9 * 40) + 12,x
            inx
            jmp draw_rs_label

draw_rs_value:
            ; Draw score value
            lda score_lo
            sta work_lo
            lda score_hi
            sta work_hi

            ldx #0
rs_div_10000:
            lda work_lo
            sec
            sbc #<10000
            tay
            lda work_hi
            sbc #>10000
            bcc rs_done_10000
            sta work_hi
            sty work_lo
            inx
            jmp rs_div_10000
rs_done_10000:
            txa
            ora #$30
            sta SCREEN + (9 * 40) + 23

            ldx #0
rs_div_1000:
            lda work_lo
            sec
            sbc #<1000
            tay
            lda work_hi
            sbc #>1000
            bcc rs_done_1000
            sta work_hi
            sty work_lo
            inx
            jmp rs_div_1000
rs_done_1000:
            txa
            ora #$30
            sta SCREEN + (9 * 40) + 24

            ldx #0
rs_div_100:
            lda work_lo
            sec
            sbc #100
            bcc rs_done_100
            sta work_lo
            inx
            jmp rs_div_100
rs_done_100:
            txa
            ora #$30
            sta SCREEN + (9 * 40) + 25

            ldx #0
rs_div_10:
            lda work_lo
            sec
            sbc #10
            bcc rs_done_10
            sta work_lo
            inx
            jmp rs_div_10
rs_done_10:
            txa
            ora #$30
            sta SCREEN + (9 * 40) + 26

            lda work_lo
            ora #$30
            sta SCREEN + (9 * 40) + 27

            lda #7
            sta COLRAM + (9 * 40) + 23
            sta COLRAM + (9 * 40) + 24
            sta COLRAM + (9 * 40) + 25
            sta COLRAM + (9 * 40) + 26
            sta COLRAM + (9 * 40) + 27

            ; Draw perfect count
            ldx #0
draw_perfect_label:
            lda perfect_label,x
            beq draw_perfect_value
            sta SCREEN + (11 * 40) + 12,x
            lda #1
            sta COLRAM + (11 * 40) + 12,x
            inx
            jmp draw_perfect_label

draw_perfect_value:
            lda perfect_count
            ldx #0
pv_div_10:
            cmp #10
            bcc pv_done_10
            sec
            sbc #10
            inx
            jmp pv_div_10
pv_done_10:
            pha
            txa
            ora #$30
            sta SCREEN + (11 * 40) + 23
            pla
            ora #$30
            sta SCREEN + (11 * 40) + 24
            lda #1
            sta COLRAM + (11 * 40) + 23
            sta COLRAM + (11 * 40) + 24

            ; Draw good count
            ldx #0
draw_good_label:
            lda good_label,x
            beq draw_good_value
            sta SCREEN + (12 * 40) + 12,x
            lda #7
            sta COLRAM + (12 * 40) + 12,x
            inx
            jmp draw_good_label

draw_good_value:
            lda good_count
            ldx #0
gv_div_10:
            cmp #10
            bcc gv_done_10
            sec
            sbc #10
            inx
            jmp gv_div_10
gv_done_10:
            pha
            txa
            ora #$30
            sta SCREEN + (12 * 40) + 23
            pla
            ora #$30
            sta SCREEN + (12 * 40) + 24
            lda #7
            sta COLRAM + (12 * 40) + 23
            sta COLRAM + (12 * 40) + 24

            ; Draw miss count
            ldx #0
draw_miss_label_r:
            lda miss_label_r,x
            beq draw_miss_value
            sta SCREEN + (13 * 40) + 12,x
            lda #2
            sta COLRAM + (13 * 40) + 12,x
            inx
            jmp draw_miss_label_r

draw_miss_value:
            lda miss_count
            ldx #0
mv_div_10:
            cmp #10
            bcc mv_done_10
            sec
            sbc #10
            inx
            jmp mv_div_10
mv_done_10:
            pha
            txa
            ora #$30
            sta SCREEN + (13 * 40) + 23
            pla
            ora #$30
            sta SCREEN + (13 * 40) + 24
            lda #2
            sta COLRAM + (13 * 40) + 23
            sta COLRAM + (13 * 40) + 24

            ; Draw max combo
            ldx #0
draw_maxc_label:
            lda maxcombo_label,x
            beq draw_maxc_value
            sta SCREEN + (15 * 40) + 12,x
            lda #COMBO_COL
            sta COLRAM + (15 * 40) + 12,x
            inx
            jmp draw_maxc_label

draw_maxc_value:
            lda max_combo
            ldx #0
mc_div_100:
            cmp #100
            bcc mc_done_100
            sec
            sbc #100
            inx
            jmp mc_div_100
mc_done_100:
            pha
            txa
            ora #$30
            sta SCREEN + (15 * 40) + 23

            pla
            ldx #0
mc_div_10:
            cmp #10
            bcc mc_done_10
            sec
            sbc #10
            inx
            jmp mc_div_10
mc_done_10:
            pha
            txa
            ora #$30
            sta SCREEN + (15 * 40) + 24
            pla
            ora #$30
            sta SCREEN + (15 * 40) + 25
            lda #COMBO_COL
            sta COLRAM + (15 * 40) + 23
            sta COLRAM + (15 * 40) + 24
            sta COLRAM + (15 * 40) + 25

            ; Check if this was a new high score
            lda new_high_score
            beq res_show_prev_hs

            ; Draw "NEW HIGH SCORE!" in bright cyan
            ldx #0
res_draw_new_hs:
            lda new_hs_text,x
            bne +                   ; Branch over jump if not zero
            jmp res_draw_return     ; Far jump when zero
+           sta SCREEN + (17 * 40) + 12,x
            lda #3                  ; Cyan
            sta COLRAM + (17 * 40) + 12,x
            inx
            jmp res_draw_new_hs

res_show_prev_hs:
            ; Draw "HIGH SCORE:" label
            ldx #0
res_draw_hs_label:
            lda high_score_text,x
            beq res_draw_hs_value
            sta SCREEN + (17 * 40) + 12,x
            lda #11                 ; Dark grey
            sta COLRAM + (17 * 40) + 12,x
            inx
            jmp res_draw_hs_label

res_draw_hs_value:
            ; Get high score for current song/difficulty
            jsr get_high_score

            ; Display 5-digit high score
            ldx #0
res_hs_div_10000:
            lda work_lo
            sec
            sbc #<10000
            tay
            lda work_hi
            sbc #>10000
            bcc res_hs_done_10000
            sta work_hi
            sty work_lo
            inx
            jmp res_hs_div_10000
res_hs_done_10000:
            txa
            ora #$30
            sta SCREEN + (17 * 40) + 24

            ldx #0
res_hs_div_1000:
            lda work_lo
            sec
            sbc #<1000
            tay
            lda work_hi
            sbc #>1000
            bcc res_hs_done_1000
            sta work_hi
            sty work_lo
            inx
            jmp res_hs_div_1000
res_hs_done_1000:
            txa
            ora #$30
            sta SCREEN + (17 * 40) + 25

            ldx #0
res_hs_div_100:
            lda work_lo
            sec
            sbc #100
            bcc res_hs_done_100
            sta work_lo
            inx
            jmp res_hs_div_100
res_hs_done_100:
            txa
            ora #$30
            sta SCREEN + (17 * 40) + 26

            ldx #0
res_hs_div_10:
            lda work_lo
            sec
            sbc #10
            bcc res_hs_done_10
            sta work_lo
            inx
            jmp res_hs_div_10
res_hs_done_10:
            txa
            ora #$30
            sta SCREEN + (17 * 40) + 27

            lda work_lo
            ora #$30
            sta SCREEN + (17 * 40) + 28

            ; Colour high score digits
            lda #11
            sta COLRAM + (17 * 40) + 24
            sta COLRAM + (17 * 40) + 25
            sta COLRAM + (17 * 40) + 26
            sta COLRAM + (17 * 40) + 27
            sta COLRAM + (17 * 40) + 28

            ; Draw best streak on row 19
            ldx #0
draw_streak_label:
            lda streak_label,x
            beq draw_streak_value
            sta SCREEN + (19 * 40) + 12,x
            lda #3                  ; Cyan
            sta COLRAM + (19 * 40) + 12,x
            inx
            jmp draw_streak_label

draw_streak_value:
            ; Display best_streak as 2-digit number
            lda best_streak
            ldx #0
streak_div_10:
            cmp #10
            bcc streak_done_10
            sec
            sbc #10
            inx
            jmp streak_div_10
streak_done_10:
            pha
            txa
            ora #$30
            sta SCREEN + (19 * 40) + 25
            pla
            ora #$30
            sta SCREEN + (19 * 40) + 26
            lda #3
            sta COLRAM + (19 * 40) + 25
            sta COLRAM + (19 * 40) + 26

res_draw_return:
            ; Draw "PRESS FIRE" on row 21
            ldx #0
draw_return:
            lda return_text,x
            beq results_done
            sta SCREEN + (21 * 40) + 10,x
            lda #11
            sta COLRAM + (21 * 40) + 10,x
            inx
            jmp draw_return

results_done:
            rts

complete_text:
            !scr "song complete!"
            !byte 0

results_score_label:
            !scr "final score:"
            !byte 0

perfect_label:
            !scr "perfect:"
            !byte 0

good_label:
            !scr "good:"
            !byte 0

miss_label_r:
            !scr "misses:"
            !byte 0

grade_label:
            !scr "grade:"
            !byte 0

streak_label:
            !scr "best streak:"
            !byte 0

maxcombo_label:
            !scr "max combo:"
            !byte 0

return_text:
            !scr "press fire to continue"
            !byte 0

; ----------------------------------------------------------------------------
; Show Game Over Screen
; ----------------------------------------------------------------------------

show_gameover:
            ; Check if we beat the high score
            jsr check_high_score

            ; Clear screen
            ldx #0
            lda #CHAR_SPACE
clear_gameover:
            sta SCREEN,x
            sta SCREEN+$100,x
            sta SCREEN+$200,x
            sta SCREEN+$2E8,x
            inx
            bne clear_gameover

            ; Draw "GAME OVER"
            ldx #0
draw_gameover_text:
            lda gameover_text,x
            beq draw_gameover_score
            sta SCREEN + (8 * 40) + 15,x
            lda #2
            sta COLRAM + (8 * 40) + 15,x
            inx
            jmp draw_gameover_text

draw_gameover_score:
            ; Draw score
            ldx #0
draw_go_score_label:
            lda gameover_score,x
            beq draw_go_score_value
            sta SCREEN + (12 * 40) + 12,x
            lda #7
            sta COLRAM + (12 * 40) + 12,x
            inx
            jmp draw_go_score_label

draw_go_score_value:
            lda score_lo
            sta work_lo
            lda score_hi
            sta work_hi

            ldx #0
go_div_10000:
            lda work_lo
            sec
            sbc #<10000
            tay
            lda work_hi
            sbc #>10000
            bcc go_done_10000
            sta work_hi
            sty work_lo
            inx
            jmp go_div_10000
go_done_10000:
            txa
            ora #$30
            sta SCREEN + (12 * 40) + 23

            ldx #0
go_div_1000:
            lda work_lo
            sec
            sbc #<1000
            tay
            lda work_hi
            sbc #>1000
            bcc go_done_1000
            sta work_hi
            sty work_lo
            inx
            jmp go_div_1000
go_done_1000:
            txa
            ora #$30
            sta SCREEN + (12 * 40) + 24

            ldx #0
go_div_100:
            lda work_lo
            sec
            sbc #100
            bcc go_done_100
            sta work_lo
            inx
            jmp go_div_100
go_done_100:
            txa
            ora #$30
            sta SCREEN + (12 * 40) + 25

            ldx #0
go_div_10:
            lda work_lo
            sec
            sbc #10
            bcc go_done_10
            sta work_lo
            inx
            jmp go_div_10
go_done_10:
            txa
            ora #$30
            sta SCREEN + (12 * 40) + 26

            lda work_lo
            ora #$30
            sta SCREEN + (12 * 40) + 27

            lda #7
            sta COLRAM + (12 * 40) + 23
            sta COLRAM + (12 * 40) + 24
            sta COLRAM + (12 * 40) + 25
            sta COLRAM + (12 * 40) + 26
            sta COLRAM + (12 * 40) + 27

            ; Check if this was a new high score
            lda new_high_score
            beq draw_prev_high

            ; Draw "NEW HIGH SCORE!" in bright cyan
            ldx #0
draw_new_hs:
            lda new_hs_text,x
            bne +                   ; Branch over jump if not zero
            jmp go_draw_retry       ; Far jump when zero
+           sta SCREEN + (14 * 40) + 12,x
            lda #3                  ; Cyan
            sta COLRAM + (14 * 40) + 12,x
            inx
            jmp draw_new_hs

draw_prev_high:
            ; Draw "HIGH SCORE:" label
            ldx #0
draw_hs_label:
            lda high_score_text,x
            beq draw_hs_value
            sta SCREEN + (14 * 40) + 12,x
            lda #11                 ; Dark grey
            sta COLRAM + (14 * 40) + 12,x
            inx
            jmp draw_hs_label

draw_hs_value:
            ; Get current high score into work_lo/work_hi
            jsr get_high_score

            ; Draw 5-digit high score value
            ldx #0
hs_div_10000:
            lda work_lo
            sec
            sbc #<10000
            tay
            lda work_hi
            sbc #>10000
            bcc hs_done_10000
            sta work_hi
            sty work_lo
            inx
            jmp hs_div_10000
hs_done_10000:
            txa
            ora #$30
            sta SCREEN + (14 * 40) + 24

            ldx #0
hs_div_1000:
            lda work_lo
            sec
            sbc #<1000
            tay
            lda work_hi
            sbc #>1000
            bcc hs_done_1000
            sta work_hi
            sty work_lo
            inx
            jmp hs_div_1000
hs_done_1000:
            txa
            ora #$30
            sta SCREEN + (14 * 40) + 25

            ldx #0
hs_div_100:
            lda work_lo
            sec
            sbc #100
            bcc hs_done_100
            sta work_lo
            inx
            jmp hs_div_100
hs_done_100:
            txa
            ora #$30
            sta SCREEN + (14 * 40) + 26

            ldx #0
hs_div_10:
            lda work_lo
            sec
            sbc #10
            bcc hs_done_10
            sta work_lo
            inx
            jmp hs_div_10
hs_done_10:
            txa
            ora #$30
            sta SCREEN + (14 * 40) + 27

            lda work_lo
            ora #$30
            sta SCREEN + (14 * 40) + 28

            ; Colour high score digits
            lda #11
            sta COLRAM + (14 * 40) + 24
            sta COLRAM + (14 * 40) + 25
            sta COLRAM + (14 * 40) + 26
            sta COLRAM + (14 * 40) + 27
            sta COLRAM + (14 * 40) + 28

go_draw_retry:
            ; Draw retry message
            ldx #0
draw_retry:
            lda retry_text,x
            beq gameover_done
            sta SCREEN + (18 * 40) + 10,x
            lda #11
            sta COLRAM + (18 * 40) + 10,x
            inx
            jmp draw_retry

gameover_done:
            rts

gameover_text:
            !scr "game over"
            !byte 0

gameover_score:
            !scr "your score:"
            !byte 0

new_hs_text:
            !scr "new high score!"
            !byte 0

high_score_text:
            !scr "high score:"
            !byte 0

retry_text:
            !scr "press fire to continue"
            !byte 0

; ============================================================================
; SONG 1 DATA - "First Steps" (120 BPM) - Three Difficulty Variants
; ============================================================================
; Easy: Notes only on strong beats (0, 8, 16, 24...) - 8 notes
; Normal: Base pattern - 29 notes
; Hard: Dense pattern with off-beat additions - 45 notes

; --- EASY: Sparse pattern (strong beats only) ---
song1_easy:
            !byte 0, 1, $47          ; Beat 0
            !byte 8, 2, $27          ; Beat 8
            !byte 16, 1, $35         ; Beat 16
            !byte 24, 3, $11         ; Beat 24
            !byte 32, 2, $2F         ; Beat 32
            !byte 40, 1, $58         ; Beat 40
            !byte 48, 3, $1A         ; Beat 48
            !byte 56, 1, $47         ; Beat 56
            !byte $FF

; --- NORMAL: Base pattern ---
song1_normal:
            !byte 0, 1, $47
            !byte 2, 2, $2C
            !byte 4, 3, $11
            !byte 8, 1, $3B
            !byte 10, 2, $27
            !byte 12, 3, $13
            !byte 16, 1, $35
            !byte 17, 2, $2C
            !byte 18, 1, $3B
            !byte 20, 3, $16
            !byte 24, 1, $47
            !byte 26, 2, $35
            !byte 28, 3, $11
            !byte 32, 2, $2F
            !byte 34, 1, $4F
            !byte 36, 3, $17
            !byte 40, 1, $58
            !byte 42, 2, $2C
            !byte 44, 3, $11
            !byte 46, 2, $27
            !byte 48, 1, $6A
            !byte 49, 2, $35
            !byte 50, 1, $58
            !byte 52, 3, $1A
            !byte 54, 2, $2F
            !byte 56, 1, $47
            !byte 58, 2, $2C
            !byte 60, 3, $11
            !byte 62, 1, $35
            !byte $FF

; --- HARD: Dense pattern (added off-beats) ---
song1_hard:
            !byte 0, 1, $47
            !byte 1, 3, $16          ; Added
            !byte 2, 2, $2C
            !byte 3, 1, $35          ; Added
            !byte 4, 3, $11
            !byte 5, 2, $27          ; Added
            !byte 6, 1, $3B          ; Added
            !byte 8, 1, $3B
            !byte 9, 3, $13          ; Added
            !byte 10, 2, $27
            !byte 11, 1, $35         ; Added
            !byte 12, 3, $13
            !byte 14, 2, $2C         ; Added
            !byte 16, 1, $35
            !byte 17, 2, $2C
            !byte 18, 1, $3B
            !byte 19, 3, $11         ; Added
            !byte 20, 3, $16
            !byte 21, 1, $47         ; Added
            !byte 22, 2, $35         ; Added
            !byte 24, 1, $47
            !byte 25, 3, $16         ; Added
            !byte 26, 2, $35
            !byte 27, 1, $4F         ; Added
            !byte 28, 3, $11
            !byte 30, 2, $27         ; Added
            !byte 32, 2, $2F
            !byte 33, 1, $47         ; Added
            !byte 34, 1, $4F
            !byte 35, 3, $11         ; Added
            !byte 36, 3, $17
            !byte 38, 2, $2C         ; Added
            !byte 40, 1, $58
            !byte 41, 3, $1A         ; Added
            !byte 42, 2, $2C
            !byte 43, 1, $47         ; Added
            !byte 44, 3, $11
            !byte 45, 2, $35         ; Added
            !byte 46, 2, $27
            !byte 47, 1, $4F         ; Added
            !byte 48, 1, $6A
            !byte 49, 2, $35
            !byte 50, 1, $58
            !byte 51, 3, $16         ; Added
            !byte 52, 3, $1A
            !byte 53, 1, $47         ; Added
            !byte 54, 2, $2F
            !byte 55, 3, $11         ; Added
            !byte 56, 1, $47
            !byte 57, 2, $27         ; Added
            !byte 58, 2, $2C
            !byte 59, 1, $3B         ; Added
            !byte 60, 3, $11
            !byte 61, 2, $35         ; Added
            !byte 62, 1, $35
            !byte 63, 3, $16         ; Added
            !byte $FF

; ============================================================================
; SONG 2 DATA - "Upbeat Groove" (130 BPM) - Three Difficulty Variants
; ============================================================================
; Easy: Notes only on strong beats - 8 notes
; Normal: Base syncopated pattern - 52 notes
; Hard: Extremely dense pattern - 72 notes

; --- EASY: Sparse pattern (strong beats only) ---
song2_easy:
            !byte 0, 1, $47          ; Beat 0
            !byte 8, 3, $11          ; Beat 8
            !byte 16, 1, $4F         ; Beat 16
            !byte 24, 2, $47         ; Beat 24
            !byte 32, 1, $4F         ; Beat 32
            !byte 40, 3, $1A         ; Beat 40
            !byte 48, 3, $11         ; Beat 48
            !byte 56, 1, $47         ; Beat 56
            !byte $FF

; --- NORMAL: Base syncopated pattern ---
song2_normal:
            !byte 0, 1, $47
            !byte 1, 2, $35
            !byte 3, 3, $16
            !byte 4, 1, $4F
            !byte 5, 2, $2C
            !byte 7, 1, $47
            !byte 8, 3, $11
            !byte 10, 2, $35
            !byte 11, 1, $58
            !byte 13, 2, $2C
            !byte 14, 3, $1A
            !byte 15, 1, $47
            !byte 16, 1, $4F
            !byte 17, 2, $35
            !byte 18, 3, $16
            !byte 19, 1, $58
            !byte 20, 2, $2C
            !byte 22, 1, $6A
            !byte 23, 2, $35
            !byte 24, 3, $11
            !byte 25, 1, $58
            !byte 26, 2, $47
            !byte 28, 3, $1A
            !byte 30, 1, $47
            !byte 31, 2, $2C
            !byte 32, 1, $4F
            !byte 33, 3, $16
            !byte 34, 2, $35
            !byte 35, 1, $58
            !byte 36, 3, $11
            !byte 37, 1, $6A
            !byte 38, 2, $47
            !byte 39, 1, $58
            !byte 40, 3, $1A
            !byte 41, 2, $35
            !byte 42, 1, $4F
            !byte 43, 2, $2C
            !byte 44, 1, $47
            !byte 45, 3, $16
            !byte 46, 2, $35
            !byte 47, 1, $58
            !byte 48, 3, $11
            !byte 49, 2, $47
            !byte 51, 1, $4F
            !byte 52, 3, $1A
            !byte 54, 2, $35
            !byte 55, 1, $47
            !byte 56, 3, $11
            !byte 58, 2, $2C
            !byte 59, 1, $58
            !byte 60, 3, $16
            !byte 61, 2, $35
            !byte 62, 1, $47
            !byte $FF

; --- HARD: Extremely dense pattern ---
song2_hard:
            !byte 0, 1, $47
            !byte 0, 3, $11          ; Chord
            !byte 1, 2, $35
            !byte 2, 1, $4F          ; Added
            !byte 3, 3, $16
            !byte 4, 1, $4F
            !byte 4, 2, $2C          ; Chord
            !byte 5, 2, $2C
            !byte 6, 3, $1A          ; Added
            !byte 7, 1, $47
            !byte 8, 3, $11
            !byte 8, 1, $58          ; Chord
            !byte 9, 2, $35          ; Added
            !byte 10, 2, $35
            !byte 11, 1, $58
            !byte 12, 3, $16         ; Added
            !byte 13, 2, $2C
            !byte 14, 3, $1A
            !byte 15, 1, $47
            !byte 16, 1, $4F
            !byte 16, 3, $11         ; Chord
            !byte 17, 2, $35
            !byte 18, 3, $16
            !byte 19, 1, $58
            !byte 20, 2, $2C
            !byte 20, 1, $47         ; Chord
            !byte 21, 3, $1A         ; Added
            !byte 22, 1, $6A
            !byte 23, 2, $35
            !byte 24, 3, $11
            !byte 24, 1, $4F         ; Chord
            !byte 25, 1, $58
            !byte 26, 2, $47
            !byte 27, 3, $16         ; Added
            !byte 28, 3, $1A
            !byte 28, 1, $58         ; Chord
            !byte 29, 2, $35         ; Added
            !byte 30, 1, $47
            !byte 31, 2, $2C
            !byte 32, 1, $4F
            !byte 32, 3, $11         ; Chord
            !byte 33, 3, $16
            !byte 34, 2, $35
            !byte 35, 1, $58
            !byte 36, 3, $11
            !byte 36, 2, $47         ; Chord
            !byte 37, 1, $6A
            !byte 38, 2, $47
            !byte 39, 1, $58
            !byte 40, 3, $1A
            !byte 40, 1, $4F         ; Chord
            !byte 41, 2, $35
            !byte 42, 1, $4F
            !byte 43, 2, $2C
            !byte 44, 1, $47
            !byte 44, 3, $11         ; Chord
            !byte 45, 3, $16
            !byte 46, 2, $35
            !byte 47, 1, $58
            !byte 48, 3, $11
            !byte 48, 1, $6A         ; Chord
            !byte 49, 2, $47
            !byte 50, 1, $58         ; Added
            !byte 51, 1, $4F
            !byte 52, 3, $1A
            !byte 52, 2, $35         ; Chord
            !byte 53, 1, $47         ; Added
            !byte 54, 2, $35
            !byte 55, 1, $47
            !byte 56, 3, $11
            !byte 56, 1, $4F         ; Chord
            !byte 57, 2, $47         ; Added
            !byte 58, 2, $2C
            !byte 59, 1, $58
            !byte 60, 3, $16
            !byte 60, 1, $47         ; Chord
            !byte 61, 2, $35
            !byte 62, 1, $47
            !byte 63, 3, $11         ; Added
            !byte $FF

; ============================================================================
; SONG 3 DATA: "Speed Rush" (150 BPM)
; ============================================================================
; An energetic, fast-paced track designed to test reaction time.
; Higher frequencies and rapid note sequences.

; --- EASY: Strong beats only (8 notes) ---
song3_easy:
            !byte 0, 1, $58          ; Beat 0
            !byte 8, 2, $35          ; Beat 8
            !byte 16, 3, $1A         ; Beat 16
            !byte 24, 1, $6A         ; Beat 24
            !byte 32, 2, $47         ; Beat 32
            !byte 40, 3, $16         ; Beat 40
            !byte 48, 1, $58         ; Beat 48
            !byte 56, 2, $35         ; Beat 56
            !byte $FF

; --- NORMAL: Base pattern (32 notes) ---
song3_normal:
            !byte 0, 1, $58
            !byte 2, 2, $35
            !byte 4, 3, $1A
            !byte 6, 1, $6A
            !byte 8, 2, $35
            !byte 10, 1, $58
            !byte 12, 3, $16
            !byte 14, 2, $47
            !byte 16, 3, $1A
            !byte 18, 1, $6A
            !byte 20, 2, $35
            !byte 22, 3, $16
            !byte 24, 1, $6A
            !byte 26, 2, $47
            !byte 28, 3, $1A
            !byte 30, 1, $58
            !byte 32, 2, $47
            !byte 34, 1, $58
            !byte 36, 3, $16
            !byte 38, 2, $35
            !byte 40, 3, $16
            !byte 42, 1, $6A
            !byte 44, 2, $47
            !byte 46, 3, $1A
            !byte 48, 1, $58
            !byte 50, 2, $35
            !byte 52, 3, $16
            !byte 54, 1, $6A
            !byte 56, 2, $35
            !byte 58, 3, $1A
            !byte 60, 1, $58
            !byte 62, 2, $47
            !byte $FF

; --- HARD: Dense pattern with rapid-fire sections (56 notes) ---
song3_hard:
            !byte 0, 1, $58
            !byte 1, 3, $1A          ; Added
            !byte 2, 2, $35
            !byte 3, 1, $6A          ; Added
            !byte 4, 3, $1A
            !byte 5, 2, $47          ; Added
            !byte 6, 1, $6A
            !byte 7, 3, $16          ; Added
            !byte 8, 2, $35
            !byte 9, 1, $58          ; Added
            !byte 10, 1, $58
            !byte 11, 2, $35         ; Added
            !byte 12, 3, $16
            !byte 13, 1, $6A         ; Added
            !byte 14, 2, $47
            !byte 15, 3, $1A         ; Added
            !byte 16, 3, $1A
            !byte 17, 1, $58         ; Added
            !byte 18, 1, $6A
            !byte 19, 2, $35         ; Added
            !byte 20, 2, $35
            !byte 21, 3, $16         ; Added
            !byte 22, 3, $16
            !byte 23, 1, $58         ; Added
            !byte 24, 1, $6A
            !byte 25, 2, $47         ; Added
            !byte 26, 2, $47
            !byte 27, 3, $1A         ; Added
            !byte 28, 3, $1A
            !byte 29, 1, $6A         ; Added
            !byte 30, 1, $58
            !byte 31, 2, $35         ; Added
            !byte 32, 2, $47
            !byte 33, 3, $16         ; Added
            !byte 34, 1, $58
            !byte 35, 2, $47         ; Added
            !byte 36, 3, $16
            !byte 37, 1, $6A         ; Added
            !byte 38, 2, $35
            !byte 39, 3, $1A         ; Added
            !byte 40, 3, $16
            !byte 41, 1, $58         ; Added
            !byte 42, 1, $6A
            !byte 43, 2, $35         ; Added
            !byte 44, 2, $47
            !byte 45, 3, $16         ; Added
            !byte 46, 3, $1A
            !byte 47, 1, $6A         ; Added
            !byte 48, 1, $58
            !byte 49, 2, $47         ; Added
            !byte 50, 2, $35
            !byte 51, 3, $1A         ; Added
            !byte 52, 3, $16
            !byte 53, 1, $58         ; Added
            !byte 54, 1, $6A
            !byte 55, 2, $35         ; Added
            !byte 56, 2, $35
            !byte 57, 3, $16         ; Added
            !byte 58, 3, $1A
            !byte 59, 1, $6A         ; Added
            !byte 60, 1, $58
            !byte 61, 2, $47         ; Added
            !byte 62, 2, $47
            !byte 63, 3, $1A         ; Added
            !byte $FF

; ============================================================================
; SONG 4: "Off-Beat" (125 BPM) - Syncopated rhythms
; ============================================================================
; Syncopation emphasises off-beats. Notes land between expected beats,
; challenging players' sense of rhythm. The pattern feels "pushed" forward.

; --- EASY: Sparse syncopated notes (10 notes) ---
; Only the most prominent off-beat accents
song4_easy:
            !byte 1, 1, $47          ; Off-beat accent
            !byte 9, 2, $3B          ; Pushed note
            !byte 17, 3, $27         ; Syncopated hit
            !byte 25, 1, $47         ; Off-beat
            !byte 33, 2, $3B         ; Pushed
            !byte 41, 3, $27         ; Syncopated
            !byte 49, 1, $47         ; Off-beat
            !byte 55, 2, $3B         ; Pushed
            !byte 59, 3, $27         ; Syncopated
            !byte 63, 1, $47         ; Final accent
            !byte $FF

; --- NORMAL: Syncopated pattern with anchors (32 notes) ---
; Mix of on-beat anchors and off-beat accents
song4_normal:
            !byte 0, 1, $47          ; Anchor
            !byte 1, 2, $3B          ; Off-beat
            !byte 3, 3, $27          ; Syncopated
            !byte 4, 1, $47          ; Anchor
            !byte 5, 2, $3B          ; Off-beat
            !byte 7, 3, $27          ; Syncopated
            !byte 8, 1, $47          ; Anchor
            !byte 9, 2, $3B          ; Off-beat
            !byte 11, 3, $27         ; Syncopated
            !byte 12, 1, $47         ; Anchor
            !byte 13, 2, $3B         ; Off-beat
            !byte 15, 3, $27         ; Syncopated
            !byte 16, 1, $47         ; Anchor
            !byte 17, 2, $3B         ; Off-beat
            !byte 19, 3, $27         ; Syncopated
            !byte 20, 1, $47         ; Anchor
            !byte 21, 2, $3B         ; Off-beat
            !byte 23, 3, $27         ; Syncopated
            !byte 24, 1, $47         ; Anchor
            !byte 25, 2, $3B         ; Off-beat
            !byte 27, 3, $27         ; Syncopated
            !byte 28, 1, $47         ; Anchor
            !byte 29, 2, $3B         ; Off-beat
            !byte 31, 3, $27         ; Syncopated
            !byte 32, 1, $47         ; Anchor
            !byte 33, 2, $3B         ; Off-beat
            !byte 35, 3, $27         ; Syncopated
            !byte 40, 1, $47         ; Anchor
            !byte 41, 2, $3B         ; Off-beat
            !byte 48, 1, $47         ; Anchor
            !byte 49, 3, $27         ; Off-beat
            !byte 56, 1, $47         ; Final anchor
            !byte $FF

; --- HARD: Dense syncopated chaos (56 notes) ---
; Nearly every off-beat filled, minimal anchors
song4_hard:
            !byte 0, 1, $47          ; Anchor
            !byte 1, 2, $3B          ; Off-beat
            !byte 1, 3, $27          ; Chord
            !byte 3, 1, $47          ; Syncopated
            !byte 3, 2, $3B          ; Chord
            !byte 5, 3, $27          ; Off-beat
            !byte 5, 1, $47          ; Chord
            !byte 7, 2, $3B          ; Syncopated
            !byte 7, 3, $27          ; Chord
            !byte 8, 1, $47          ; Anchor
            !byte 9, 2, $3B          ; Off-beat
            !byte 9, 3, $27          ; Chord
            !byte 11, 1, $47         ; Syncopated
            !byte 11, 2, $3B         ; Chord
            !byte 13, 3, $27         ; Off-beat
            !byte 13, 1, $47         ; Chord
            !byte 15, 2, $3B         ; Syncopated
            !byte 15, 3, $27         ; Chord
            !byte 16, 1, $47         ; Anchor
            !byte 17, 2, $3B         ; Off-beat
            !byte 17, 3, $27         ; Chord
            !byte 19, 1, $47         ; Syncopated
            !byte 19, 2, $3B         ; Chord
            !byte 21, 3, $27         ; Off-beat
            !byte 21, 1, $47         ; Chord
            !byte 23, 2, $3B         ; Syncopated
            !byte 23, 3, $27         ; Chord
            !byte 24, 1, $47         ; Anchor
            !byte 25, 2, $3B         ; Off-beat
            !byte 25, 3, $27         ; Chord
            !byte 27, 1, $47         ; Syncopated
            !byte 27, 2, $3B         ; Chord
            !byte 29, 3, $27         ; Off-beat
            !byte 29, 1, $47         ; Chord
            !byte 31, 2, $3B         ; Syncopated
            !byte 31, 3, $27         ; Chord
            !byte 32, 1, $47         ; Anchor
            !byte 33, 2, $3B         ; Off-beat
            !byte 35, 3, $27         ; Syncopated
            !byte 37, 1, $47         ; Off-beat
            !byte 39, 2, $3B         ; Syncopated
            !byte 40, 1, $47         ; Anchor
            !byte 41, 3, $27         ; Off-beat
            !byte 43, 2, $3B         ; Syncopated
            !byte 45, 1, $47         ; Off-beat
            !byte 47, 3, $27         ; Syncopated
            !byte 48, 1, $47         ; Anchor
            !byte 49, 2, $3B         ; Off-beat
            !byte 51, 3, $27         ; Syncopated
            !byte 53, 1, $47         ; Off-beat
            !byte 55, 2, $3B         ; Syncopated
            !byte 56, 1, $47         ; Anchor
            !byte 57, 3, $27         ; Off-beat
            !byte 59, 2, $3B         ; Syncopated
            !byte 61, 1, $47         ; Off-beat
            !byte 63, 3, $27         ; Final syncopated
            !byte $FF

; ============================================================================
; SONG 5: "Chordstorm" (136 BPM) - Polyrhythms
; ============================================================================
; Polyrhythms feature multiple notes on the same beat across different tracks,
; creating chord-like moments that require simultaneous key presses.

; --- EASY: Sparse chords (12 notes) ---
; Simple two-note chords on strong beats
song5_easy:
            !byte 0, 1, $47          ; Track 1
            !byte 0, 2, $3B          ; Track 2 (chord)
            !byte 8, 2, $3B          ; Single
            !byte 16, 1, $47         ; Track 1
            !byte 16, 3, $27         ; Track 3 (chord)
            !byte 24, 3, $27         ; Single
            !byte 32, 1, $47         ; Track 1
            !byte 32, 2, $3B         ; Track 2 (chord)
            !byte 40, 2, $3B         ; Single
            !byte 48, 2, $3B         ; Track 2
            !byte 48, 3, $27         ; Track 3 (chord)
            !byte 56, 1, $47         ; Single
            !byte $FF

; --- NORMAL: Mixed chords and singles (36 notes) ---
; Regular chord patterns with some singles
song5_normal:
            !byte 0, 1, $47          ; 3-note chord
            !byte 0, 2, $3B
            !byte 0, 3, $27
            !byte 4, 1, $47          ; Single
            !byte 8, 2, $3B          ; 2-note chord
            !byte 8, 3, $27
            !byte 12, 2, $3B         ; Single
            !byte 16, 1, $47         ; 3-note chord
            !byte 16, 2, $3B
            !byte 16, 3, $27
            !byte 20, 3, $27         ; Single
            !byte 24, 1, $47         ; 2-note chord
            !byte 24, 2, $3B
            !byte 28, 1, $47         ; Single
            !byte 32, 2, $3B         ; 3-note chord
            !byte 32, 1, $47
            !byte 32, 3, $27
            !byte 36, 2, $3B         ; Single
            !byte 40, 1, $47         ; 2-note chord
            !byte 40, 3, $27
            !byte 44, 3, $27         ; Single
            !byte 48, 1, $47         ; 3-note chord
            !byte 48, 2, $3B
            !byte 48, 3, $27
            !byte 52, 1, $47         ; Single
            !byte 56, 2, $3B         ; 2-note chord
            !byte 56, 3, $27
            !byte 60, 1, $47         ; Single
            !byte $FF

; --- HARD: Dense chord patterns (64 notes) ---
; Frequent chords with minimal breaks
song5_hard:
            !byte 0, 1, $47          ; 3-note chord
            !byte 0, 2, $3B
            !byte 0, 3, $27
            !byte 2, 1, $47          ; 2-note chord
            !byte 2, 2, $3B
            !byte 4, 2, $3B          ; 3-note chord
            !byte 4, 1, $47
            !byte 4, 3, $27
            !byte 6, 3, $27          ; 2-note chord
            !byte 6, 1, $47
            !byte 8, 1, $47          ; 3-note chord
            !byte 8, 2, $3B
            !byte 8, 3, $27
            !byte 10, 2, $3B         ; Single
            !byte 12, 1, $47         ; 2-note chord
            !byte 12, 3, $27
            !byte 14, 2, $3B         ; 3-note chord
            !byte 14, 1, $47
            !byte 14, 3, $27
            !byte 16, 1, $47         ; 3-note chord
            !byte 16, 2, $3B
            !byte 16, 3, $27
            !byte 18, 3, $27         ; 2-note chord
            !byte 18, 2, $3B
            !byte 20, 1, $47         ; 2-note chord
            !byte 20, 2, $3B
            !byte 22, 2, $3B         ; 3-note chord
            !byte 22, 1, $47
            !byte 22, 3, $27
            !byte 24, 1, $47         ; 3-note chord
            !byte 24, 2, $3B
            !byte 24, 3, $27
            !byte 26, 1, $47         ; Single
            !byte 28, 2, $3B         ; 2-note chord
            !byte 28, 3, $27
            !byte 30, 1, $47         ; 3-note chord
            !byte 30, 2, $3B
            !byte 30, 3, $27
            !byte 32, 1, $47         ; 3-note chord
            !byte 32, 2, $3B
            !byte 32, 3, $27
            !byte 34, 2, $3B         ; 2-note chord
            !byte 34, 3, $27
            !byte 36, 1, $47         ; 2-note chord
            !byte 36, 2, $3B
            !byte 38, 3, $27         ; 3-note chord
            !byte 38, 1, $47
            !byte 38, 2, $3B
            !byte 40, 2, $3B         ; 3-note chord
            !byte 40, 1, $47
            !byte 40, 3, $27
            !byte 42, 1, $47         ; Single
            !byte 44, 2, $3B         ; 2-note chord
            !byte 44, 3, $27
            !byte 46, 1, $47         ; 3-note chord
            !byte 46, 2, $3B
            !byte 46, 3, $27
            !byte 48, 1, $47         ; 3-note chord
            !byte 48, 2, $3B
            !byte 48, 3, $27
            !byte 52, 2, $3B         ; 2-note chord
            !byte 52, 1, $47
            !byte 56, 1, $47         ; 3-note chord
            !byte 56, 2, $3B
            !byte 56, 3, $27
            !byte 60, 3, $27         ; 2-note chord
            !byte 60, 2, $3B
            !byte 62, 1, $47         ; Final 3-note chord
            !byte 62, 2, $3B
            !byte 62, 3, $27
            !byte $FF

; ============================================================================
; Song 6 - Fusion (130 BPM)
; ============================================================================
; Combines basic rhythm, syncopation, and polyrhythms
; Tests all skills developed throughout the game

; --- EASY: Learning mixed techniques (16 notes) ---
song6_easy:
            !byte 0, 1, $47          ; Strong beat
            !byte 4, 2, $3B          ; Basic rhythm
            !byte 8, 1, $47
            !byte 8, 2, $3B          ; Simple 2-note chord
            !byte 14, 3, $27         ; Off-beat (syncopation)
            !byte 16, 1, $47         ; Strong beat
            !byte 22, 2, $3B         ; Off-beat
            !byte 24, 3, $27
            !byte 28, 1, $47
            !byte 32, 1, $47
            !byte 32, 3, $27         ; 2-note chord
            !byte 38, 2, $3B         ; Off-beat
            !byte 40, 1, $47
            !byte 46, 3, $27         ; Off-beat
            !byte 48, 2, $3B
            !byte 56, 1, $47         ; Final note
            !byte $FF

; --- NORMAL: Balanced mix (32 notes) ---
song6_normal:
            ; Section 1: Basic + syncopation
            !byte 0, 1, $47          ; Strong downbeat
            !byte 2, 2, $3B          ; Quick follow
            !byte 6, 3, $27          ; Off-beat
            !byte 8, 1, $47          ; Beat
            !byte 10, 2, $3B
            !byte 14, 1, $47         ; Syncopation
            ; Section 2: Add chords
            !byte 16, 1, $47         ; 2-note chord
            !byte 16, 2, $3B
            !byte 20, 3, $27
            !byte 22, 2, $3B         ; Off-beat
            !byte 24, 1, $47
            !byte 26, 3, $27
            !byte 30, 2, $3B         ; Off-beat
            ; Section 3: Full mix
            !byte 32, 1, $47         ; 3-note chord
            !byte 32, 2, $3B
            !byte 32, 3, $27
            !byte 36, 1, $47
            !byte 38, 2, $3B         ; Off-beat
            !byte 40, 3, $27
            !byte 44, 1, $47
            !byte 46, 2, $3B         ; Off-beat
            ; Section 4: Climax
            !byte 48, 1, $47         ; 2-note chord
            !byte 48, 3, $27
            !byte 50, 2, $3B
            !byte 52, 1, $47
            !byte 54, 3, $27         ; Off-beat
            !byte 56, 1, $47         ; 2-note chord
            !byte 56, 2, $3B
            !byte 58, 3, $27
            !byte 60, 1, $47
            !byte 62, 2, $3B         ; Final off-beat
            !byte $FF

; --- HARD: All techniques combined (56 notes) ---
song6_hard:
            ; Section 1: Fast basic + syncopation
            !byte 0, 1, $47
            !byte 1, 2, $3B
            !byte 2, 3, $27
            !byte 3, 1, $58          ; Off-beat
            !byte 4, 2, $3B
            !byte 6, 1, $47          ; Off-beat
            !byte 7, 3, $27
            !byte 8, 1, $47          ; 2-note chord
            !byte 8, 2, $3B
            !byte 10, 3, $27
            !byte 11, 1, $47         ; Syncopation
            !byte 12, 2, $3B
            !byte 14, 1, $58         ; Off-beat
            !byte 15, 3, $27
            ; Section 2: Chord-heavy
            !byte 16, 1, $47         ; 3-note chord
            !byte 16, 2, $3B
            !byte 16, 3, $27
            !byte 18, 1, $58
            !byte 19, 2, $3B         ; Off-beat
            !byte 20, 1, $47         ; 2-note chord
            !byte 20, 3, $27
            !byte 22, 2, $3B         ; Off-beat
            !byte 23, 1, $47
            !byte 24, 2, $3B         ; 2-note chord
            !byte 24, 3, $27
            !byte 26, 1, $58
            !byte 27, 2, $3B         ; Off-beat
            !byte 28, 1, $47
            !byte 30, 3, $27         ; Off-beat
            ; Section 3: Maximum challenge
            !byte 32, 1, $47         ; 3-note chord
            !byte 32, 2, $3B
            !byte 32, 3, $27
            !byte 34, 1, $58         ; 2-note chord
            !byte 34, 2, $3B
            !byte 35, 3, $27         ; Off-beat
            !byte 36, 1, $47
            !byte 38, 2, $3B         ; Off-beat
            !byte 39, 3, $27
            !byte 40, 1, $47         ; 3-note chord
            !byte 40, 2, $3B
            !byte 40, 3, $27
            !byte 42, 1, $58
            !byte 43, 2, $3B         ; Off-beat
            !byte 44, 3, $27         ; 2-note chord
            !byte 44, 1, $47
            !byte 46, 2, $3B         ; Off-beat
            ; Section 4: Grand finale
            !byte 48, 1, $47         ; 3-note chord
            !byte 48, 2, $3B
            !byte 48, 3, $27
            !byte 50, 1, $58
            !byte 51, 2, $3B         ; Off-beat
            !byte 52, 1, $47         ; 2-note chord
            !byte 52, 3, $27
            !byte 54, 2, $3B
            !byte 55, 1, $47         ; Off-beat
            !byte 56, 1, $47         ; Final 3-note chord
            !byte 56, 2, $3B
            !byte 56, 3, $27
            !byte $FF

; ----------------------------------------------------------------------------
; Note Arrays
; ----------------------------------------------------------------------------

note_track:
            !fill MAX_NOTES, 0

note_col:
            !fill MAX_NOTES, 0

note_freq:
            !fill MAX_NOTES, 0

; ----------------------------------------------------------------------------
; Game Variables
; ----------------------------------------------------------------------------

score_lo:     !byte 0
score_hi:     !byte 0
miss_count:   !byte 0
perfect_count: !byte 0
good_count:   !byte 0
health:       !byte 0
combo:        !byte 0
max_combo:    !byte 0
new_high_score: !byte 0          ; Flag: 1 if current score beat high score

; ----------------------------------------------------------------------------
; High Score Table (6 songs x 3 difficulties = 18 entries, 2 bytes each)
; Index = (song * 3) + difficulty
; ----------------------------------------------------------------------------

high_scores:
            ; Song 1 (First Steps)
            !word 0               ; Easy high score
            !word 0               ; Normal high score
            !word 0               ; Hard high score
            ; Song 2 (Upbeat Groove)
            !word 0               ; Easy
            !word 0               ; Normal
            !word 0               ; Hard
            ; Song 3 (Speed Rush)
            !word 0               ; Easy
            !word 0               ; Normal
            !word 0               ; Hard
            ; Song 4 (Off-Beat)
            !word 0               ; Easy
            !word 0               ; Normal
            !word 0               ; Hard
            ; Song 5 (Chordstorm)
            !word 0               ; Easy
            !word 0               ; Normal
            !word 0               ; Hard
            ; Song 6 (Fusion)
            !word 0               ; Easy
            !word 0               ; Normal
            !word 0               ; Hard

; ============================================================================
; END OF SID SYMPHONY - UNIT 31
; ============================================================================

What’s Coming

With results now showing meaningful performance feedback:

  • Unit 32: Phase 2 completion and polish

What You’ve Learnt

  • Percentage calculation - Multiplication and division in 8-bit assembly
  • Binary multiplication - Using shifts instead of loops for speed
  • Repeated subtraction - Simple division for small values
  • Grade systems - Translating numbers to meaningful feedback
  • Colour coding - Using colour for instant recognition
  • Layout design - Organising statistics for readability

Grades transform scores into understanding.

What Changed

Unit 30 → Unit 31
+231-9
11 ; ============================================================================
2-; SID SYMPHONY - Unit 30: Advanced Scoring Mechanics
2+; SID SYMPHONY - Unit 31: Results Screen Enhancements
33 ; ============================================================================
4-; Adding advanced scoring: perfect streak bonuses, section completion rewards,
5-; and full combo bonus. Skilled play is now richly rewarded.
4+; Enhancing the results screen with performance grades (S/A/B/C/D), accuracy
5+; percentage, best streak display, and improved statistics layout.
66 ;
7-; New concepts: Streak tracking, milestone bonuses, accuracy rewards
7+; New concepts: Grade calculation, percentage display, enhanced UI layout
88 ;
99 ; Controls: Z = Track 1, X = Track 2, C = Track 3
1010 ; Up/Down = Song selection, Left/Right = Difficulty
...
108108
109109 ; Full combo bonus (no misses)
110110 FULL_COMBO_BONUS = 200 ; Bonus if no misses entire song
111+
112+; Performance grade thresholds (based on accuracy percentage)
113+; Accuracy = (perfect_count * 100 + good_count * 50) / total_notes
114+GRADE_S_THRESHOLD = 95 ; 95%+ perfect rate = S rank
115+GRADE_A_THRESHOLD = 85 ; 85%+ = A rank
116+GRADE_B_THRESHOLD = 70 ; 70%+ = B rank
117+GRADE_C_THRESHOLD = 50 ; 50%+ = C rank
118+ ; Below 50% = D rank
111119
112120 ; ============================================================================
113121 ; COMBO SETTINGS (BALANCED)
...
339347 best_streak = $20 ; Best perfect streak this song
340348 section_bonus = $21 ; Accumulated section bonus points
341349 notes_hit = $22 ; Total notes hit (for accuracy calc)
350+current_grade = $23 ; Current performance grade letter (screen code)
351+current_grade_col = $24 ; Colour for current grade
342352
343353 ; ----------------------------------------------------------------------------
344354 ; BASIC Stub
...
36883698 bne flash_t3h_loop
36893699 lda #1
36903700 sta COLRAM + (TRACK3_ROW * 40)
3701+ rts
3702+
3703+; ----------------------------------------------------------------------------
3704+; Calculate Performance Grade
3705+; ----------------------------------------------------------------------------
3706+; Calculates grade based on perfect hit percentage
3707+; Sets current_grade (screen code) and current_grade_col (colour)
3708+
3709+calculate_grade:
3710+ ; Calculate perfect percentage: (perfect_count * 100) / total_notes
3711+ ; For simplicity, use perfect_count vs notes_hit ratio
3712+ ; notes_hit = perfect_count + good_count
3713+ ; We want: perfect_count / notes_hit * 100
3714+
3715+ ; If no notes hit, give D grade
3716+ lda notes_hit
3717+ bne calc_grade_continue
3718+ lda #'d' ; Screen code for D
3719+ sta current_grade
3720+ lda #2 ; Red
3721+ sta current_grade_col
3722+ rts
3723+
3724+calc_grade_continue:
3725+ ; Calculate percentage: perfect_count * 100 / notes_hit
3726+ ; Use 8-bit approximation for simplicity
3727+
3728+ ; First multiply perfect_count by 100
3729+ lda perfect_count
3730+ sta work_lo
3731+ lda #0
3732+ sta work_hi
3733+
3734+ ; Multiply by 100 (64 + 32 + 4 = 100)
3735+ ; work = perfect * 64
3736+ asl work_lo
3737+ rol work_hi ; *2
3738+ asl work_lo
3739+ rol work_hi ; *4
3740+ asl work_lo
3741+ rol work_hi ; *8
3742+ asl work_lo
3743+ rol work_hi ; *16
3744+ asl work_lo
3745+ rol work_hi ; *32
3746+ asl work_lo
3747+ rol work_hi ; *64
3748+
3749+ ; Save *64 result
3750+ lda work_lo
3751+ sta ZP_TEMP
3752+ lda work_hi
3753+ pha ; Save high byte of *64
3754+
3755+ ; Get *32 (shift right from *64)
3756+ lsr work_hi
3757+ ror work_lo ; *32 in work
3758+
3759+ ; Add *64 + *32 = *96
3760+ lda ZP_TEMP
3761+ clc
3762+ adc work_lo
3763+ sta work_lo
3764+ pla
3765+ adc work_hi
3766+ sta work_hi ; work = perfect * 96
3767+
3768+ ; Add *4 to get *100
3769+ lda perfect_count
3770+ asl
3771+ asl ; A = perfect * 4
3772+ clc
3773+ adc work_lo
3774+ sta work_lo
3775+ lda #0
3776+ adc work_hi
3777+ sta work_hi ; work = perfect * 100
3778+
3779+ ; Now divide by notes_hit
3780+ ; Simple repeated subtraction for small values
3781+ lda #0
3782+ sta ZP_TEMP ; Quotient (percentage)
3783+
3784+grade_div_loop:
3785+ ; Check if work >= notes_hit
3786+ lda work_hi
3787+ bne grade_can_sub ; High byte nonzero, can subtract
3788+ lda work_lo
3789+ cmp notes_hit
3790+ bcc grade_div_done ; work < notes_hit, done
3791+grade_can_sub:
3792+ ; Subtract notes_hit from work
3793+ lda work_lo
3794+ sec
3795+ sbc notes_hit
3796+ sta work_lo
3797+ lda work_hi
3798+ sbc #0
3799+ sta work_hi
3800+ inc ZP_TEMP ; Increment percentage
3801+ lda ZP_TEMP
3802+ cmp #101 ; Cap at 100%
3803+ bcc grade_div_loop
3804+grade_div_done:
3805+ ; ZP_TEMP now contains percentage (0-100)
3806+ ; Determine grade based on thresholds
3807+ lda ZP_TEMP
3808+ cmp #GRADE_S_THRESHOLD
3809+ bcs grade_is_s
3810+ cmp #GRADE_A_THRESHOLD
3811+ bcs grade_is_a
3812+ cmp #GRADE_B_THRESHOLD
3813+ bcs grade_is_b
3814+ cmp #GRADE_C_THRESHOLD
3815+ bcs grade_is_c
3816+ ; Below C threshold = D
3817+ lda #'d'
3818+ sta current_grade
3819+ lda #2 ; Red
3820+ sta current_grade_col
3821+ rts
3822+
3823+grade_is_s:
3824+ lda #'s'
3825+ sta current_grade
3826+ lda #7 ; Yellow
3827+ sta current_grade_col
3828+ rts
3829+
3830+grade_is_a:
3831+ lda #'a'
3832+ sta current_grade
3833+ lda #5 ; Green
3834+ sta current_grade_col
3835+ rts
3836+
3837+grade_is_b:
3838+ lda #'b'
3839+ sta current_grade
3840+ lda #3 ; Cyan
3841+ sta current_grade_col
3842+ rts
3843+
3844+grade_is_c:
3845+ lda #'c'
3846+ sta current_grade
3847+ lda #15 ; Light grey
3848+ sta current_grade_col
36913849 rts
36923850
36933851 ; ----------------------------------------------------------------------------
...
37213879 ldx #0
37223880 draw_complete:
37233881 lda complete_text,x
3724- beq draw_results_score
3882+ beq draw_grade
37253883 sta SCREEN + (5 * 40) + 13,x
37263884 lda #5
37273885 sta COLRAM + (5 * 40) + 13,x
37283886 inx
37293887 jmp draw_complete
3888+
3889+draw_grade:
3890+ ; Calculate and display performance grade
3891+ jsr calculate_grade
3892+ ; Display grade label
3893+ ldx #0
3894+draw_grade_label:
3895+ lda grade_label,x
3896+ beq draw_grade_letter
3897+ sta SCREEN + (7 * 40) + 14,x
3898+ lda #7
3899+ sta COLRAM + (7 * 40) + 14,x
3900+ inx
3901+ jmp draw_grade_label
3902+
3903+draw_grade_letter:
3904+ ; Grade letter is in A register from calculate_grade
3905+ lda current_grade
3906+ sta SCREEN + (7 * 40) + 22
3907+ ; Colour based on grade
3908+ ldx current_grade_col
3909+ stx COLRAM + (7 * 40) + 22
37303910
37313911 draw_results_score:
37323912 ; Draw score label
...
40784258 sta COLRAM + (17 * 40) + 26
40794259 sta COLRAM + (17 * 40) + 27
40804260 sta COLRAM + (17 * 40) + 28
4261+
4262+ ; Draw best streak on row 19
4263+ ldx #0
4264+draw_streak_label:
4265+ lda streak_label,x
4266+ beq draw_streak_value
4267+ sta SCREEN + (19 * 40) + 12,x
4268+ lda #3 ; Cyan
4269+ sta COLRAM + (19 * 40) + 12,x
4270+ inx
4271+ jmp draw_streak_label
4272+
4273+draw_streak_value:
4274+ ; Display best_streak as 2-digit number
4275+ lda best_streak
4276+ ldx #0
4277+streak_div_10:
4278+ cmp #10
4279+ bcc streak_done_10
4280+ sec
4281+ sbc #10
4282+ inx
4283+ jmp streak_div_10
4284+streak_done_10:
4285+ pha
4286+ txa
4287+ ora #$30
4288+ sta SCREEN + (19 * 40) + 25
4289+ pla
4290+ ora #$30
4291+ sta SCREEN + (19 * 40) + 26
4292+ lda #3
4293+ sta COLRAM + (19 * 40) + 25
4294+ sta COLRAM + (19 * 40) + 26
40814295
40824296 res_draw_return:
4083- ; Draw "PRESS FIRE"
4297+ ; Draw "PRESS FIRE" on row 21
40844298 ldx #0
40854299 draw_return:
40864300 lda return_text,x
40874301 beq results_done
4088- sta SCREEN + (20 * 40) + 10,x
4302+ sta SCREEN + (21 * 40) + 10,x
40894303 lda #11
4090- sta COLRAM + (20 * 40) + 10,x
4304+ sta COLRAM + (21 * 40) + 10,x
40914305 inx
40924306 jmp draw_return
40934307
...
41124326
41134327 miss_label_r:
41144328 !scr "misses:"
4329+ !byte 0
4330+
4331+grade_label:
4332+ !scr "grade:"
4333+ !byte 0
4334+
4335+streak_label:
4336+ !scr "best streak:"
41154337 !byte 0
41164338
41174339 maxcombo_label:
...
52225444 !word 0 ; Hard
52235445
52245446 ; ============================================================================
5225-; END OF SID SYMPHONY - UNIT 30
5447+; END OF SID SYMPHONY - UNIT 31
52265448 ; ============================================================================
52275449