Press Space and the byte advances once. Hold it and it stays put; release it before the next action. Escape returns to BASIC. The firmware keeps doing its job while your game waits.

Put it in your project
Build from the repository root:
python3 scripts/build-msx-gameboy-loops.py --assembler /path/to/asm198x --system msxKeep demo.asm and the three includes together. Enter generated build/working/load.bas under the normal recorded MSX1 NTSC BIOS/BASIC; it reserves memory with CLEAR 200,49151, reads/POKEs the actual 164-byte image at $C000–$C0A3 and defines USR at $C000. Allow RUN to finish before entering SCREEN 0:WIDTH 40:CLS:A=USR(0). The verifier requires exact loaded-byte equality before entry, rather than assuming typing completion means the BASIC loader completed.
Origin, settings and includes
; SPDX-License-Identifier: MIT
org $C000
INITIAL equ 0
STEP equ 1
RESET_EACH equ 0
NO_EDGE equ 0
include "caller.inc"
include "routines.inc"
include "presentation.inc"Startup and the machine-specific outer loop
; BASIC reserves this image with CLEAR 200,49151 and calls DEF USR=&HC000.
; Normal MSX1 BIOS/BASIC mapping and enabled firmware interrupts required.
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 and adapters
turn_init: A becomes the labelled turn_count byte. A and flags are preserved. Call once at startup. turn_advance: add compile-time byte STEP modulo 256; changes A, flags and turn_count. Neither routine reads input, draws or checks time. Include each implementation once in your own build, supplying origin and settings as demo.asm does.
The small image includes its state byte and eight-byte mutable display buffer. CLEAR reserves the entire area above BASIC’s new ceiling, including unused space; it is not a claim that BASIC loses only 164 bytes. Do not overlap another buffer or code image. The normal BIOS/BASIC mapping and enabled firmware interrupts must remain available; no slot or mapper is changed here.
new_action uses BIOS SNSMAT ($0141): row 8 bit 0 is Space; row 7 bit 2 is Escape; zero means down. SNSMAT and the command reader change AF. The reader waits for Space release before accepting a fresh action, so a key already held at startup is not immediately accepted. KILBUF ($0156) clears buffered firmware repeats before return. This is two-command input, not a general keyboard scanner or autorepeat policy.
show_turn forms TURN $hh in the image’s eight-byte buffer and calls BIOS LDIRVM ($005C) with HL=source, DE=0, BC=8. It writes the first eight cells of the selected SCREEN 0 default name table and leaves the cursor alone. Treat all main registers/flags as clobbered by this adapter; the top-level caller saves/restores AF/BC/DE/HL/IX/IY around the complete session. Those saves use 12 stack bytes plus nested helper/BIOS/interrupt needs on the existing balanced BASIC stack. The caller returns through RET, not a jump into a guessed interpreter address.
The displayed byte is hexadecimal: $0A is ten, $FF is 255, then a unit step wraps to $00. Change the presentation adapter for another mode or table base. BIOS access preserves manufacturer-specific hardware handling; it does not make an arbitrary display layout portable.
Rule state and the input/wait adapters
; A is the initial byte. A preserved; LD leaves flags unchanged.
turn_init:
ld (turn_count),a
ret
; Byte addition modulo 256. A/flags and turn_count change; no I/O.
turn_advance:
ld a,(turn_count)
add a,STEP
ld (turn_count),a
ret
; BIOS SNSMAT: A=row, returns active-low bits, AF changed.
; Space is row 8 bit 0; Escape is row 7 bit 2.
; Return A=1 for action, A=0 for quit; AF changed. Escape wins.
new_action:
ld a,NO_EDGE
or a
jr nz,wait_key
release_space:
ld a,8
call $0141
and 1
jr z,release_space
wait_key:
ld a,7
call $0141
bit 2,a
jr z,quit_key
ld a,8
call $0141
and 1
jr nz,wait_key
ld a,1
ret
quit_key:
ld a,7
call $0141
bit 2,a
jr z,quit_key
call $0156 ; KILBUF: discard firmware repeats before BASIC resumes
xor a
ret
turn_count:
db 0The native output adapter
; Qualified SCREEN 0, 40 columns, default name table at VRAM $0000.
; BIOS LDIRVM copies eight bytes; all main registers/flags may change.
; Caller owns these eight cells and label's two mutable digits, not the cursor.
show_turn:
ld a,(turn_count)
rrca
rrca
rrca
rrca
and 15
call hex_digit
ld (label+6),a
ld a,(turn_count)
and 15
call hex_digit
ld (label+7),a
ld hl,label
ld de,0
ld bc,8
call $005C
ret
hex_digit:
cp 10
jr c,digit
add a,7
digit:
add a,48
ret
label:
db "TURN $00"| Routine | Contract |
|---|---|
turn_init |
Store A in count; preserve A and flags. |
turn_advance |
Add STEP modulo 256; change A, flags and count. |
new_action |
Return A=1 for a new Space press, 0 for Escape; change AF. |
show_turn |
Change eight display cells, two buffer bytes and main registers/flags. |
Customise and diagnose
customise.asm uses STEP=3: two actions give 0 → 3 → 6. One action still invokes the rule once. The byte contract needs a whole-number starting value and step from 0 through 255. Adding them gives at most 510, so one subtraction of 256 suffices in BASIC; the CPU’s byte addition wraps naturally in assembly. The nonzero check starts at 100 with step 255 and reaches 99 then 98.
Three progress units for one accepted action
; SPDX-License-Identifier: MIT
org $C000
INITIAL equ 0
STEP equ 3
RESET_EACH equ 0
NO_EDGE equ 0
include "caller.inc"
include "routines.inc"
include "presentation.inc"
mistakes/reset-each-command.asm is deliberately broken: initialisation runs inside the repeated path, so the next iteration erases progress. Keep initialisation at startup and jump back to the command/update path to repair it. The repaired caller is checked in a separate cold start.
Deliberately broken: reset inside the repeated path
; Deliberately broken: erase rule state inside the iteration.
; SPDX-License-Identifier: MIT
org $C000
INITIAL equ 0
STEP equ 1
RESET_EACH equ 1
NO_EDGE equ 0
include "caller.inc"
include "routines.inc"
include "presentation.inc"
--variant no-edge deliberately removes release gating. A held Space then spends repeated turns and must fail the normal press guard. Deliberate auto-repeat needs an explicit delay and repeat interval.
An untimed world waits for an accepted action. A timed turn needs its own clock, expiry policy and ordering when a command arrives at the deadline. Display refresh or firmware interrupt activity does not automatically advance game rules. Blocking input is useful when nothing else needs main-loop service; continuing animation, music or deadlines require a different input/service policy.
Related choices
Compare the MSX BASIC caller when a submitted line suits your commands.
The loop choices explain untimed actions, timed turns and display servicing as separate decisions.
Verification
Native-emulator executed: Emu198x 0.44.1, Asm198x 0.0.71, on the configuration above. The source README gives the reproduction command; the retained record identifies the exact inputs and tools.
The six common cold starts check normal use, step three, reset fault/repair, wraparound and nonzero initialisation. Idle, first accepted action, waiting/holding, ignored input and a second accepted action are retained as native captures and actual observations. Assembly adds a seventh no-edge cold start, full exact payload readback before USR, actual VDP I/O write traces (address zero followed by eight character bytes), and working BASIC arithmetic after return. Raw I/O traces are bounded to one native frame and 4096 sampled events; retained records identify the VDP-write subset. The state byte and the actual emitted character bytes must agree.
scripts/check-msx-gameboy-loops.py independently decodes the records; falsification tests reject missing cases/stages, altered source/capture/payload/counts and false repairs. The additive three-route inventory does not expand the broad input/drawing/sound probe count or qualify new audio. No original-hardware, cassette-transfer, other model/firmware or whole-emulator claim follows.
The maintained source includes the complete caller, settings, customisation and deliberate fault. The retained record binds the executed source, observations, native images and released-tool identities.
Primary references
ASCII/Microsoft, MSX Technical Data Book: Advanced Programming Guide BIOS entries SNSMAT ($0141), KILBUF ($0156), LDIRVM ($005C); BASIC Reference Guide CLEAR and DEFUSR/USR. The MSX standard requires BIOS access for portable non-VDP I/O; this caller also uses BIOS for video transfer.