Skip to content

Game Loop (HALT)

Frame-synchronised game loop using the HALT instruction. Locks to 50Hz for consistent timing across all Spectrums.

Taught in Game 1, Unit 1 game-looptiminghaltinterrupt

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

AspectCost
CPUFree (waits for interrupt)
Memory1 byte (halt = $76)
LimitationTied 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

  1. The Z80's HALT instruction suspends the CPU
  2. 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)
  3. When the interrupt fires, HALT releases and execution continues
  4. Your game code runs, then hits HALT again 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.

Patterns: Keyboard Reading

Vault: ULA — frame timing and memory contention | Z80 — HALT instruction | ZX Spectrum