Overview
The simplest way to achieve consistent timing on the ZX Spectrum. The HALT instruction pauses execution until the next interrupt fires (50 times per second on PAL systems). This gives you exactly one frame to process input, update game state, and render.
Code
; =============================================================================
; GAME LOOP - HALT SYNCHRONISED
; 50Hz frame-locked game loop for ZX Spectrum
; Taught: Game 1 (Ink War), Unit 1
; CPU: Variable | Memory: ~20 bytes
; =============================================================================
; Main game loop structure
main_loop:
halt ; Wait for next interrupt (50Hz)
call read_input ; Check keyboard/joystick
call update_game ; Update game state
call draw_screen ; Render graphics
jp main_loop ; Repeat forever
; Placeholder routines (replace with your game code)
read_input:
ret
update_game:
ret
draw_screen:
ret
Minimal working example:
org 32768
start:
call init_game
main_loop:
halt ; Sync to 50Hz
call read_keyboard
call move_player
jp main_loop
init_game:
; Your initialisation here
ret
read_keyboard:
; Your input code here
ret
move_player:
; Your movement code here
ret
end start
Trade-offs
| Aspect | Cost |
|---|---|
| CPU | Free (waits for interrupt) |
| Memory | 1 byte (halt = $76) |
| Limitation | Tied to 50Hz - can't run faster or slower |
When to use: Most games. Simple, reliable, works on all Spectrums.
When to avoid: When you need sub-frame timing (scrollers, raster effects) or variable frame rates.
How It Works
- The Z80's
HALTinstruction suspends the CPU - The ULA generates an interrupt 50 times per second (the standard ZX Spectrum is PAL — rare 60Hz NTSC variants existed but aren't what most coders target)
- When the interrupt fires,
HALTreleases and execution continues - Your game code runs, then hits
HALTagain to wait for the next frame
This guarantees your game runs at a consistent speed regardless of how fast your code executes (as long as it completes within one frame).
Frame Budget
At 50 Hz on a 3.5 MHz Z80, you have exactly:
- 69,888 T-states per frame (3,500,000 ÷ 50.0801)
- ~17,472 instructions if every instruction averages 4 T-states
- Enough for most games — but watch contended memory
ULA contention
When the beam draws the visible display (lines 64-255), the ULA periodically halts CPU access to memory in $4000-$7FFF (display memory). This contention adds ~6 T-states per access during display, and your code costs more if it lives in contended memory.
To avoid the penalty: place game code at $8000+ (uncontended), or run timing-critical code only during the border / vblank period when the ULA isn't competing for memory access.
Related
Patterns: Keyboard Reading
Vault: ULA — frame timing and memory contention | Z80 — HALT instruction | ZX Spectrum