Build bbc-micro/patterns/assembly/input/mos-controls, type the generated build/loader.bas in the recorded BASIC, then RUN. Move with Q/A/O/P; Space restarts. Holding a direction keeps moving; holding Space must allow movement after the first restart.

Take the reader
Copy the input routine with its two tables and private key_index byte. Define OSBYTE=$FFF4 and provide one byte called controls. The caller shows read_controls followed by detect_edges; previous, pressed and released each need their own byte. Initialise previous before the first read.
The reusable reader
; MOS OSBYTE 129: negative INKEY scans. No key-buffer consumption.
; Returns controls bits U/D/L/R/action=1/2/4/8/16; changes A/X/Y/flags.
read_controls:
lda #0
sta controls
ldx #0
key_next:
stx key_index
lda #129
ldy #$ff
lda key_numbers,x
tax
lda #129
jsr OSBYTE
txa
ldx key_index
cmp #0
beq key_up
lda controls
ora key_masks,x
sta controls
key_up:
inx
cpx #5
bne key_next
rts
key_numbers: !byte $ef,$be,$c9,$c8,$9d ; Q/A/O/P/Space (-17/-66/-55/-56/-99)
key_masks: !byte 1,2,4,8,16
key_index: !byte 0read_controls scans Q/A/O/P/Space through MOS OSBYTE 129. It returns controls bits 1/2/4/8/16 for up/down/left/right/action, without consuming the character buffer. A/X/Y/flags change; key_index is private scratch.
Call from main with MOS IRQ services active. The routine samples five keys without draining the character buffer. Its private scratch makes it non-reentrant; an interrupt must not call it halfway through another scan.
Change it and recognise a mistake
Change the key-number/mask tables together. Read once, then derive pressed/released before replacing previous.
If a held Space keeps restarting the player, the action path is testing controls instead of pressed, or clearing previous during reset. The working caller is checked while Space stays held and a direction is pressed: movement must still occur. Keep held directions and restart edges separate.
The complete caller
Working setup and integration
; BBC Model B MOS 1.20/BASIC II. Typed loader, then CALL &3000.
; Q/A/O/P move, Space restarts. MOS owns IRQ and SOUND queue; reset to leave.
*=$3000
OSWRCH=$ffee
OSWORD=$fff1
start:
cli
jsr reset_game
main_loop:
jsr wait_frame
jsr read_controls
jsr detect_edges
jsr update_player
jsr draw_screen
inc updates
frame_done:
jmp main_loop
updates: !byte 0
!source "state.inc"
!source "rules.inc"
!source "input.inc"
!source "draw.inc"
!source "sound.inc"
!source "../../framework/game-loop/frame.inc"
!source "collision.inc"For more responsive controls, compare polling, buffering and press edges in input choices.
Verification and sources
Target: BBC Model B MOS 1.20/BASIC II, MODE 6. These callers were executed with released Emu198x 0.44.1; assembly uses Asm198x 0.0.71. The sample folder contains reproduction instructions and retained observations. The independent checker binds source, native captures and actual output; its rejection tests exercise wrong state, missing files and false audio claims.
This qualifies the named emulator/firmware path. Pitch accuracy, original hardware and other peripherals require their own observations. The muted MOS run is an explicit diagnostic RAM patch to RTS at trigger_sound, restored after the check.
Acorn, BBC Microcomputer Advanced User Guide / Electron technical documentation: OSBYTE &81 negative INKEY and OSWORD &07 SOUND block; MOS IRQ ownership.