Build the complete caller with make -C tatung-einstein/patterns/assembly/input/read-action. Press Space once, keep it held, then release and press again. The piece moves on each new press and returns to its starting position after two; holding does not repeat the action. Attach build/demo.dsk and use Control-BREAK to let MOS boot it. This is a disk-boot program, not a CP/M or BASIC loader.

Keep the world waiting
Display servicing continues while the turn count waits for a new Space press. The VDP keeps scanning its picture; wait_frame polls a fresh status announcement and the caller performs its bounded input, picture and sound work. It does not advance the rule merely because a field elapsed.
The actual loop and its machine setup
; Original capability caller: owns VDP, one audio channel and declared RAM.
VDP_DATA equ $08
VDP_CTRL equ $09
TURNS equ $8000
SOUND_TICKS equ $8005
; Stock MOS disk boot header: destination, end, entry, disk mode.
org $9000
dw $9000,$9200,start
db 0
start:
di
ld sp,$fff0
xor a
ld ($8000),a
ld ($8001),a
ld ($8002),a
ld ($8003),a
ld a,1
out ($20),a ; mask dedicated keyboard interrupt
ld a,7
out ($02),a
ld a,$7f
out ($03),a ; mute tone/noise gates, port A output / B input
in a,($09)
xor a
out ($09),a
ld a,$80
out ($09),a ; R0 legacy graphics
xor a
out ($09),a
ld a,$81
out ($09),a ; R1 display and VDP IRQ disabled
call init_picture
main:
call wait_frame
ld hl,($8002)
inc hl
ld ($8002),hl
include "sample.inc"
ld a,($8001)
cpl
and b
jr z,no_action
ld a,($8000)
inc a
ld ($8000),a
call trigger_sound
no_action:
ld a,b
ld ($8001),a
call draw_picture
call service_sound
jp main
; AF clobbered; caller exclusively owns status/control-port latch.
wait_frame:
in a,($09)
wait_new:
in a,($09)
and $80
jr z,wait_new
ret
include "picture.inc"
include "sound.inc"
defs $9200-$,0Fit and adapt it
sample.inc is an inline hardware sampler: include it where the caller samples input. It has no RET/RTS and must not be called as a subroutine. It supplies the held state described below; the caller supplies previous-state storage and decides what a new press does.
Calculate a new-press mask as current AND NOT previous, then store current as previous on every update, including release. Test held state for continuous movement and the new-press mask for an action that should happen once. A key already held at the caller’s first sample counts as a press because previous starts at zero.
Keep the dedicated keyboard interrupt masked as in startup, or synchronise all AY register selections. An interrupt between address and data accesses can redirect a write.
The broken caller feeds held state into the action decision. Keeping the button down advances repeatedly; the native fault run reaches 32 instead of remaining at one. Restore the previous-state comparison and update previous on release as well.
A repeated-path mistake to recognise
; Original capability caller: owns VDP, one audio channel and declared RAM.
VDP_DATA equ $08
VDP_CTRL equ $09
TURNS equ $8000
SOUND_TICKS equ $8005
; Stock MOS disk boot header: destination, end, entry, disk mode.
org $9000
dw $9000,$9200,start
db 0
start:
di
ld sp,$fff0
xor a
ld ($8000),a
ld ($8001),a
ld ($8002),a
ld ($8003),a
ld a,1
out ($20),a ; mask dedicated keyboard interrupt
ld a,7
out ($02),a
ld a,$7f
out ($03),a ; mute tone/noise gates, port A output / B input
in a,($09)
xor a
out ($09),a
ld a,$80
out ($09),a ; R0 legacy graphics
xor a
out ($09),a
ld a,$81
out ($09),a ; R1 display and VDP IRQ disabled
call init_picture
main:
call wait_frame
ld hl,($8002)
inc hl
ld ($8002),hl
include "../sample.inc"
ld a,($8001)
ld a,b
nop
jr z,no_action
ld a,($8000)
inc a
ld ($8000),a
call trigger_sound
no_action:
ld a,b
ld ($8001),a
call draw_picture
call service_sound
jp main
; AF clobbered; caller exclusively owns status/control-port latch.
wait_frame:
in a,($09)
wait_new:
in a,($09)
and $80
jr z,wait_new
ret
include "../picture.inc"
include "../sound.inc"
defs $9200-$,0
A timed turn is another policy: keep a deadline separately, decide how expiry competes with an arriving action, and service that clock while waiting. A blocking reader cannot provide that service from the same main path. A faster renderer or sound routine does not settle the rule.
See loop choices and the machine’s input entry for the reader’s complete contract.
Verification and sources
Target: TC-01 PAL, MOS 1.2, Ctrl-BREAK disk boot. 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.
Tatung, Einstein Hardware Manual: programmable sound generator/keyboard interface and port decoding; MOS disk boot header; General Instrument AY-3-8910 data manual.