Press Space. The backdrop changes once. Hold it and the colour stays put; release and press again to restore the first colour. The world waits for a new action while the display-service loop continues.
The backdrop shows parity, rather than an exact count: odd and even values use different colours. It is a deliberately small output adapter, leaving the rule update easy to reuse.

Build and enter the caller
Keep demo.asm, caller.inc, routines.inc and presentation.inc together. From the code-samples root:
python3 scripts/build-aquarius-mtx-loops.py --assembler /path/to/asm198x --system memotech-mtxEnter the generated build/working/load.bas line by line under normal firmware. The builder derives its DATA bytes from the actual assembled image. Follow the source README’s entry sequence after loading.
On an MTX500, the 135-byte image occupies $E000–$E086. The generated READ/POKE loader stops after loading; LET X=USR(57344) enters it. This caller takes over until reset: it disables maskable interrupts, sets SP to $F7F0 and owns the keyboard and VDP control ports. It does not return to BASIC. Reserve its code, context and stack in your own memory plan; this small loader is not a general allocator for a mixed BASIC/assembly game.
Origin, settings and includes
; SPDX-License-Identifier: MIT
org $E000
INITIAL equ 0
STEP equ 1
RESET_EACH equ 0
NO_EDGE equ 0
CTX equ $DF00
include "caller.inc"
include "routines.inc"
include "presentation.inc"Initialise once, then decide when to update
; Reuses the stock BASIC READ/POKE/USR entry, then takes over until reset.
start:
di
ld sp,$F7F0
ld a,INITIAL
call turn_init
xor a
ld (CTX+1),a
ld (CTX+2),a
ld (CTX+3),a
in a,($02)
xor a
out ($02),a
ld a,$80
out ($02),a
xor a
out ($02),a
ld a,$81
out ($02),a
call show_turn
main:
call wait_frame
ld hl,(CTX+2)
inc hl
ld (CTX+2),hl
ld a,RESET_EACH
or a
jr z,keep_state
xor a
call turn_init
keep_state:
call sample_action
or a
jr z,no_action
call turn_advance
no_action:
call show_turn
jr mainReuse the rule without the caller
Put the starting byte in A and call turn_init once. Call turn_advance only when your own command handler accepts an action. Neither routine reads a key, draws a cell or consults a clock. Include the implementation once in your build.
Persistent state and the byte update
; Reusable state operations: no display, keyboard or clock access.
; CTX owns four bytes: count, previous sample, display-service count (16-bit LE).
turn_init:
ld (CTX),a
ret
turn_advance:
ld a,(CTX)
add a,STEP
ld (CTX),a
ret
; Returns A=1 for one new Space press, otherwise zero. AF/BC changed.
; Always save the sample, including on a rejected/held action.
sample_action:
ld a,$7F
out ($05),a
in a,($06)
cpl
and 1
ld b,a
ld a,NO_EDGE
or a
ld a,b
jr nz,accept_sample
ld a,(CTX+1)
cpl
and b
accept_sample:
ld c,a
ld a,b
ld (CTX+1),a
ld a,c
ret
; Fresh VDP status edge. AF changed; exclusively owns the status latch.
wait_frame:
in a,($02)
wait_new:
in a,($02)
and $80
jr z,wait_new
ret| Routine | Contract |
|---|---|
turn_init |
Store A into count; A and flags remain unchanged. |
turn_advance |
Add compile-time STEP modulo 256; changes A, flags and count. |
Define CTX as four private RAM bytes. This caller uses $DF00–$DF03 for count, previous Space sample and a little-endian 16-bit display-service counter. turn_init changes only count; the caller initialises the other bytes once.
sample_action drives keyboard column 7 through OUT $05=$7F, reads Space on port $06 bit 0, and updates the previous sample on every call. It returns 1 only for a new press; AF/BC change. wait_frame clears stale VDP status, then waits for a fresh bit-7 indication; AF changes. It exclusively owns status/control port $02, so it cannot share that code unchanged with a firmware VDP interrupt handler or another status reader.
The service counter advances every native field while the rule count waits. It wraps after 65535. A running display clock does not automatically make this a timed game.
The machine-specific output adapter
; Alternate backdrop 2/3 using count parity. AF changed, no rule update.
; Direct control/status port ownership; no CPU-mapped text screen is implied.
show_turn:
ld a,(CTX)
and 1
add a,2
out ($02),a
ld a,$87
out ($02),a
retshow_turn selects backdrop index 2 or 3 through VDP register 7; AF changes. It blanks the character display and lets the VDP maintain refresh. This is neither a CPU-mapped text screen nor a renderer that must draw every scanline. Use a different presentation adapter when you want room descriptions, sprites or an exact numeric status.
Customise the step
Build with --variant step-three to use this alternative. Two presses now produce 0 → 3 → 6, with no extra state or image bytes.
Same caller, three units per accepted action
; SPDX-License-Identifier: MIT
org $E000
INITIAL equ 0
STEP equ 3
RESET_EACH equ 0
NO_EDGE equ 0
CTX equ $DF00
include "caller.inc"
include "routines.inc"
include "presentation.inc"
Parity still alternates for a step of three. A two-colour display cannot tell you whether a change was one or three; inspect the state byte when checking the amount.
Common mistakes: resetting and repeating
This variant is deliberately broken. RESET_EACH=1 puts initialisation inside the iteration. Each new pass erases the previous result, so the visible output returns to its initial state.
Broken: erase the previous action
; Deliberately broken.
; SPDX-License-Identifier: MIT
org $E000
INITIAL equ 0
STEP equ 1
RESET_EACH equ 1
NO_EDGE equ 0
CTX equ $DF00
include "caller.inc"
include "routines.inc"
include "presentation.inc"
Restore startup-only initialisation to repair it. The separate no-edge test variant demonstrates another fault: accepting a held key on every sample. It must fail the ordinary one-action-per-press check. Deliberate auto-repeat needs its own initial delay and repeat policy.
Where time belongs
An untimed world advances on an accepted action. A timed turn also needs a deadline and a rule for an action arriving at that deadline. Display refresh, firmware interrupts and CPU activity do not decide that policy for you.
Compare the loop choices and the BASIC implementation. The languages have different storage and ownership agreements; choose the one that suits your project.
Source and verification
Native-emulator executed: Emu198x 0.44.1, with MTX500 firmware; assembly is built with Asm198x 0.0.71. Actual keyboard input drives the program. Host writes do not supply game state. The images above are native framebuffer captures.
The maintained source includes the complete caller, reusable pieces and deliberate fault. The retained observations cover normal use, idle and held input, a custom step, reset and repair, 254 → 255 → 0, and a nonzero start. Assembly adds exact loaded-byte comparison and a disabled-edge failure control. The MTX assembly run checks the service counter during idle and compares both actual backdrop images. An independent checker rejects altered counts, sources and captures, missing observations and falsely repaired faults.
This qualifies these small workloads on the named configuration. It does not establish original-hardware, cassette-transfer, other-model or whole-emulator compatibility. The older input/drawing/sound probe remains a separate record; this loop entry makes no fresh audio claim.
Primary references
Memotech’s MTX Series BASIC Tutor, Reference and Operator’s Manual, original manual scan: BASIC control flow, USR and return value in BC. Memotech’s MTX Video Memory Mapping, document scan: separate VDP RAM, ports and access spacing. Keyboard drive/sense follows the retained stock-firmware input-loop and MTX hardware reference.