Skip to content

Wait for a turn on Sord M5

Reuse an untimed command or press loop while retaining the machine’s actual display responsibilities.

Build the complete caller with make -C sord-m5/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. Load the cartridge produced by the Makefile in the named configuration; retain its reset/header setup.

Sord M5 native caller after accepted input.
Captured from the running caller in the configuration below.

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

demo.asm
; Original capability caller: owns VDP, one audio channel and declared RAM.
VDP_DATA equ $10
VDP_CTRL equ $11
TURNS equ $7000
SOUND_TICKS equ $7005
; Sord M5 monitor-started homebrew cartridge. Space commits an untimed turn.
; Owns VDP/keyboard and $7000-$7003; stack below $7FF0; no monitor return.
    org $2000
    db 0
    dw start,init_return      ; monitor calls initialiser, then entry vector
    jp irq_return
    jp irq_return
start:
    di
    ld sp,$7ff0
    xor a
    ld ($7000),a
    ld ($7001),a
    ld ($7002),a
    ld ($7003),a
    in a,($11)
    xor a
    out ($11),a
    ld a,$80
    out ($11),a               ; R0 legacy graphics
    xor a
    out ($11),a
    ld a,$81
    out ($11),a               ; R1 display and VDP IRQ disabled
    call init_picture
main:
    call wait_frame
    ld hl,($7002)
    inc hl
    ld ($7002),hl
    include "sample.inc"
    ld a,($7001)
    cpl
    and b
    jr z,no_action
    ld a,($7000)
    inc a
    ld ($7000),a
    call trigger_sound
no_action:
    ld a,b
    ld ($7001),a
    call draw_picture
    call service_sound
    jp main
wait_frame:
    in a,($11)
wait_new:
    in a,($11)
    and $80
    jr z,wait_new
    ret
init_return:
irq_return:
    ret
    include "picture.inc"
    include "sound.inc"
    defs $6000-$,0

Fit 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.

Do not add the complement used by neighbouring Z80 machines: an idle M5 would look pressed. Other keyboard rows need their own selection/read contract.

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

no-edge.asm
; Original capability caller: owns VDP, one audio channel and declared RAM.
VDP_DATA equ $10
VDP_CTRL equ $11
TURNS equ $7000
SOUND_TICKS equ $7005
; Sord M5 monitor-started homebrew cartridge. Space commits an untimed turn.
; Owns VDP/keyboard and $7000-$7003; stack below $7FF0; no monitor return.
    org $2000
    db 0
    dw start,init_return      ; monitor calls initialiser, then entry vector
    jp irq_return
    jp irq_return
start:
    di
    ld sp,$7ff0
    xor a
    ld ($7000),a
    ld ($7001),a
    ld ($7002),a
    ld ($7003),a
    in a,($11)
    xor a
    out ($11),a
    ld a,$80
    out ($11),a               ; R0 legacy graphics
    xor a
    out ($11),a
    ld a,$81
    out ($11),a               ; R1 display and VDP IRQ disabled
    call init_picture
main:
    call wait_frame
    ld hl,($7002)
    inc hl
    ld ($7002),hl
    include "../sample.inc"
    ld a,($7001)
    ld a,b
    nop
    jr z,no_action
    ld a,($7000)
    inc a
    ld ($7000),a
    call trigger_sound
no_action:
    ld a,b
    ld ($7001),a
    call draw_picture
    call service_sound
    jp main
wait_frame:
    in a,($11)
wait_new:
    in a,($11)
    and $80
    jr z,wait_new
    ret
init_return:
irq_return:
    ret
    include "../picture.inc"
    include "../sound.inc"
    defs $6000-$,0
Sord M5 faulty repeated path.
The fault fails the same rule-state guard as the working caller.

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: NTSC M5, monitor-started cartridge. 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.

Sord BASIC-G Appendix H: I/O map; Sord M5C-01 schematic: decoding; TI SN76489A and TMS9918A data manuals. The specific Space polarity is configuration-specific executed evidence.