Press 1. The displayed byte advances once. Hold the key and it stays put; release and press again for the next update. Q returns to BASIC after you release it.

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 mattel-aquariusEnter 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 unexpanded Aquarius, CLEAR 50,15871 reserves the top of RAM for the 152-byte image at $3E00–$3E97. The loader sets the normal USR vector at 14340/14341. PRINT CHR$(11):X=USR(0) clears the display and calls it. Do not place another buffer or BASIC allocation over the reserved image.
The caller saves AF, BC, DE, HL, IX and IY, leaves the original stack and interrupt policy in place, and returns through RET. Those saves take 12 stack bytes, plus four for nested helper return addresses and the interpreter’s own needs. Q is released before return so it does not become the next BASIC command.
Origin, settings and includes
; SPDX-License-Identifier: MIT
org $3E00
INITIAL equ 0
STEP equ 1
RESET_EACH equ 0
NO_EDGE equ 0
include "caller.inc"
include "routines.inc"
include "presentation.inc"Initialise once, then decide when to update
; Reserve above CLEAR 50,15871. Enter through the normal USR vector.
start:
push af
push bc
push de
push hl
push ix
push iy
ld a,INITIAL
call turn_init
main:
ld a,RESET_EACH
or a
jr z,keep_state
xor a
call turn_init
keep_state:
call show_turn
call new_action
or a
jr z,finished
call turn_advance
jr main
finished:
pop iy
pop ix
pop hl
pop de
pop bc
pop af
retReuse 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
; A is the initial byte; A preserved, flags unchanged by LD.
turn_init:
ld (turn_count),a
ret
; Add STEP modulo 256. A/flags changed; no I/O.
turn_advance:
ld a,(turn_count)
add a,STEP
ld (turn_count),a
ret
; Sample row 7: bit 2 is 1, bit 3 is Q, both active-low.
; A=1 action / A=0 quit; BC/A/flags changed. Q wins a simultaneous sample.
new_action:
ld a,NO_EDGE
or a
jr nz,wait_key
release_keys:
ld bc,$7FFF
in a,(c)
and 12
cp 12
jr nz,release_keys
wait_key:
ld bc,$7FFF
in a,(c)
bit 3,a
jr z,quit_key
bit 2,a
jr nz,wait_key
ld a,1
ret
quit_key:
in a,(c)
bit 3,a
jr z,quit_key
xor a
ret
turn_count:
db 0| 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. |
The Aquarius count is one labelled byte inside the reserved image. new_action reads $7FFF, keyboard row 7: bit 2 is 1 and bit 3 is Q, both active-low. It requires release before accepting another action; Q wins a simultaneous sample. This is a two-command reader, rather than a full keyboard scanner. It reads the keyboard port without writing the port’s memory-scrambling control.
The machine-specific output adapter
; Normal Aquarius text screen: first usable cell is $3029.
; Writes eight cells, no cursor or colour changes. AF/BC/DE/HL changed.
show_turn:
ld hl,label
ld de,$3029
ld bc,6
ldir
ld a,(turn_count)
rrca
rrca
rrca
rrca
and 15
call hex_digit
ld ($302F),a
ld a,(turn_count)
and 15
call hex_digit
ld ($3030),a
ret
hex_digit:
cp 10
jr c,digit
add a,7
digit:
add a,48
ret
label:
db "TURN $"show_turn writes eight ASCII cells at $3029–$3030, the first usable text cells. AF/BC/DE/HL change. It leaves cursor and colour state alone. The byte is hexadecimal: $0A means ten, $FF means 255, and the next unit step wraps to $00.
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 $3E00
INITIAL equ 0
STEP equ 3
RESET_EACH equ 0
NO_EDGE equ 0
include "caller.inc"
include "routines.inc"
include "presentation.inc"
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 $3E00
INITIAL equ 0
STEP equ 1
RESET_EACH equ 1
NO_EDGE equ 0
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 Aquarius S2 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 Aquarius assembly run also returns to BASIC and evaluates arithmetic there. 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
Aquarius S2 ROM, disassembly with Kenny Millar’s annotations: USR vector, BASIC RAM boundary, keyboard row and screen layout. The executable firmware is not distributed with these samples.