Difficulty Progression
Keep the challenge fresh. Dynamic speed adjustments reward skilled players with faster, more intense gameplay.
The Boredom Problem
A fixed difficulty creates two failure modes. Beginners struggle and give up. Experts get bored and move on. Neither plays for long.
The solution: dynamic difficulty. As players demonstrate skill, increase the challenge. As they struggle, ease off. The game stays in the “flow” zone — difficult enough to engage, not so hard it frustrates.
Speed as Difficulty
In a rhythm game, note speed is the obvious difficulty lever. Faster notes demand quicker reactions. We already have a skill indicator: the multiplier. High multiplier means the player is hitting notes consistently. Reward that consistency with a greater challenge.
; Difficulty progression constants
; Lower number = faster notes (fewer frames between moves)
SPEED_1X = 4 ; Normal speed at 1x multiplier
SPEED_2X = 3 ; Faster at 2x
SPEED_3X = 3 ; Same at 3x (challenge plateau)
SPEED_4X = 2 ; Fastest at 4x multiplier
At 1x, notes move every 4 frames. At 4x, they move every 2 frames — twice as fast. The jump from 2x to 3x is a plateau, giving players a brief respite before the final push.
Table Lookup
Rather than calculate speed from multiplier each time, we use a lookup table. This is a common 6502 pattern — trading a few bytes of memory for faster execution:
; Speed lookup table (indexed by multiplier-1)
speed_table:
!byte SPEED_1X ; 1x multiplier
!byte SPEED_2X ; 2x multiplier
!byte SPEED_3X ; 3x multiplier
!byte SPEED_4X ; 4x multiplier
The multiplier ranges from 1-4, but array indices start at 0. We subtract 1 before indexing:
update_speed:
ldx multiplier
dex ; Convert 1-4 to 0-3 index
lda speed_table,x
sta current_speed
rts
Three instructions to look up and store the new speed. Fast and simple.
Triggering Speed Changes
Speed updates whenever the multiplier changes. That happens in two places: when it increases (combo threshold reached) and when it resets (miss).
inc multiplier
; Trigger border flash on multiplier increase
lda #BORDER_FLASH_TIME
sta border_flash_timer
; Update speed for new multiplier
jsr update_speed
; Reset combo and multiplier on miss
lda #$00
sta combo_count
lda #$01
sta multiplier
; Reset speed to normal
jsr update_speed
Using Current Speed
The game loop uses current_speed instead of the hardcoded NOTE_SPEED:
; Handle note movement
dec move_timer
bne ug_no_move
lda current_speed ; Use current speed based on multiplier
sta move_timer
jsr move_notes
When move_timer hits zero, we reload it from current_speed. At higher multipliers, this value is smaller, so the timer expires more often, moving notes more frequently.
Initialisation
Reset the speed variable when starting a new game:
; Reset speed
lda #SPEED_1X
sta current_speed
The Feel
Watch how this changes the game:
- Player starts at 1x — comfortable pace
- Builds combo, hits 2x — notes speed up
- Adapts to new speed, hits 3x — same speed (breathing room)
- Pushes through to 4x — notes fly
- One miss — back to 1x, sudden slowdown
The speed changes create drama. Reaching 4x feels like an achievement. Losing it feels like a fall. The game becomes a story of rises and crashes, not just a steady rhythm.
Why Not Continuous Scaling?
We could calculate speed as 5 - multiplier. But discrete steps feel better. Each multiplier level has a distinct character. Players can think “I’m at 3x speed” rather than feeling lost on a sliding scale.
The plateau at 3x is deliberate. After two speed increases, players need time to consolidate. The jump to 4x then feels special — the final challenge.
Alternative Approaches
Other ways to increase difficulty:
- More notes: Spawn notes more frequently at higher multipliers
- Narrower windows: Shrink the Perfect zone as skill increases
- Pattern complexity: Introduce chord hits earlier at higher levels
- Visual noise: Add distractions as difficulty climbs
Speed is the simplest lever. The others add complexity without proportional payoff.
What’s Next
The game now adapts to player skill. In Unit 14, we add high score persistence — saving the best score so players have a target to beat across sessions.