Skip to content

A game loop using MOS

Wait for display sync through MOS, keeping the system keyboard, display and interrupt services running.

Enter the generated BASIC loader and RUN it. A digit changes on the screen as the update counter advances. Hold Space to give the CPU too much work for one display frame; release it and the normal pace returns.

Use the wait in your project

Call wait_frame once per update. It invokes OSBYTE 19 through the documented entry point. A becomes 19; X/Y/flags are not preserved by this wrapper. The caller uses OSBYTE 129 to scan Space and OSWRCH to update a text position. That leaves hardware ownership with MOS, unlike the C64 and Amiga takeover examples.

frame.inc
; BBC Model B MOS service. Keep the MOS IRQ path and interrupts enabled.
; OSBYTE &13 waits for the next display sync. A becomes &13; X/Y/flags change.
; ROM entry point, not a replacement IRQ handler. Reuses the system's clock.
OSBYTE = $fff4
wait_frame:
    lda #19
    jmp OSBYTE

Start the complete example

The tested Model B has MOS 1.20 and BASIC II. The loader selects MODE 6 and reserves memory above HIMEM=&3000 for the assembled program. Its DATA statements install our own bytes at &3000; no ROM is supplied. Keep MOS interrupts and the IRQ service intact. Reset to leave this bounded demonstration.

With Asm198x available, keep the sample folders together and run:

Shell
make -C bbc-micro/patterns/assembly/framework/game-loop

The Amiga Makefile also needs Build198x. The BBC Makefile produces both the binary and a numbered BASIC loader; enter build/loader.bas and RUN it.

The loop keeps four jobs visible: wait, read input, update state, draw. expensive_work is an optional CPU workload to expose missed deadlines; remove it when building your own game. The changing colour or digit is a small observable result, not a rendering engine.

demo.asm
; BBC Model B, MOS 1.20/BASIC II. Enter generated loader.bas, RUN.
; Space holds slow CPU work. MOS services own IRQ/video/keyboard. Reset to leave.
*=$3000
OSWRCH=$ffee
start:
    cli
    lda #0
    sta updates
    sta updates+1
    sta slow
main_loop:
    jsr wait_frame
    jsr read_input
    jsr update_game
    jsr draw_screen
    jsr expensive_work
frame_done:
    jmp main_loop
read_input:
    lda #129
    ldx #$9d                 ; INKEY(-99): Space
    ldy #$ff
    jsr OSBYTE
    stx slow                 ; $ff held, 0 released
    rts
update_game:
    inc updates
    bne update_done
    inc updates+1
update_done:
    rts
draw_screen:
    lda #31                  ; VDU 31: fixed text position, then a changing digit
    jsr OSWRCH
    lda #10
    jsr OSWRCH
    lda #8
    jsr OSWRCH
    lda updates
    lsr
    lsr
    lsr
    and #7
    clc
    adc #48
    jmp OSWRCH
expensive_work:
    lda slow
    beq work_done
    ldy #64
work_outer:
    ldx #0
work_inner:
    dex
    bne work_inner
    dey
    bne work_outer
work_done:
    rts
updates: !word 0
slow: !byte 0
!source "frame.inc"

What happens when work runs late

Waiting does not make the game work faster. When work exceeds the display interval, update frequency falls. The helper does not supply an elapsed-frame count or replay missed updates. A replacement IRQ handler that stops MOS processing display sync can break the wait. The Electron example executes this same service-facing source with its own OS/BASIC and workload record.

Check this combination

Code status: assembled and emulator-executed. BBC Model B, pinned MOS/BASIC. The recorded checks identify sources, firmware and observer. In three 30-frame windows the caller made 30 normal, 10 deliberately slowed and 30 recovered updates. An unpaced negative control is rejected by the same rate guard. This is configuration-specific emulator evidence, not an original-hardware or whole-emulator claim.

The loop comparison puts the ownership choices side by side. Each implementation keeps its own timing and resource contract.

The original BBC Micro Advanced User Guide, printed page 135, documents OSBYTE 19 and its dependence on IRQ1 handling; page 153 covers keyboard scanning with OSBYTE 129.