Skip to content

A game loop using MOS on the Electron

Reuse the MOS-facing loop while checking the Electron’s own loading, display and workload behaviour.

Type the shared BASIC loader and RUN it. A digit changes as the game updates. Hold Space to slow it with excessive CPU work, then release it to recover.

Reuse a service contract

This is the same executable as the BBC Micro example. It calls MOS instead of reaching directly into the BBC’s VIA or the Electron’s ULA. Shared services make that reuse possible; the common CPU alone would not.

Call wait_frame once before input/update/draw. The wrapper leaves A=19 and does not preserve X/Y/flags. Keep the MOS IRQ service and interrupts enabled. The wait accepts the next display sync; it supplies no missed-frame count and does not replay updates.

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

run the following command:

Shell
make -C acorn-electron/patterns/assembly/framework/game-loop

. That builds the shared sources in bbc-micro/patterns/assembly/framework/game-loop; type its build/loader.bas on the Electron and RUN it.

The loader selects MODE 6 and reserves memory above HIMEM=&3000. It installs our own executable at &3000 using DATA statements. This is a typed program route, not a snapshot of an already-running machine. Reset to leave.

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

The MOS interface is shared, but the amount of work that fits is not assumed to match the BBC. The same deliberate workload produced fewer updates on the tested Electron. That is a result for this caller and configuration, not a ranking of the machines or an isolated CPU benchmark.

The source comparison waits for installation to finish, with a bounded timeout. It does not assume both machines’ BASIC loaders complete after the same short delay. A new graphics mode, peripheral or direct hardware routine needs its own evidence.

Check this combination

Code status: assembled and emulator-executed. Acorn Electron, pinned OS/BASIC. The recorded checks identify sources, firmware and observer. In three 30-frame windows the caller made 30 normal, 5 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.