Build the complete caller with make -C coleco-colecovision/patterns/assembly/input/read-action. Press left fire 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.

Take the reader
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.
The reusable reader
in a,($fc) ; controller 1, left fire on bit 6
cpl
and $40
ld b,aThe caller selects joystick mode with OUT ($C0),A. IN ($FC) then reads controller 1: left fire is active-low bit 6. The inline sampler returns 0/$40 in B and changes A/flags/B.
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.
Caller storage: previous=$6001; turn=$6000, field clock=$6002/$6003; stack below $63F0. The sample’s display initialiser and any interrupts have their own ownership requirements; keep them with this complete example.
Change it and recognise a mistake
Keep controller mode selection with the reader; keypad mode gives another meaning to the same read. Share mode changes explicitly.
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.
Deliberately broken input/update path
; Original capability caller: owns VDP, one audio channel and declared RAM.
VDP_DATA equ $be
VDP_CTRL equ $bf
TURNS equ $6000
SOUND_TICKS equ $6005
; ColecoVision BIOS-started cartridge. Left fire commits one untimed turn.
; Owns VDP/controller mode, RAM $6000-$6004 and stack below $63F0.
org $8000
db $55,$aa ; skip BIOS title, enter start vector
dw 0,0,0,0,start
jp unused_rst
jp unused_rst
jp unused_rst
jp unused_rst
jp unused_rst
jp unused_rst
jp unused_rst
jp nmi_return
start:
di
ld sp,$63f0
xor a
ld ($6000),a
ld ($6001),a
ld ($6002),a
ld ($6003),a
in a,($bf) ; reset control latch and clear pending flags
xor a
out ($bf),a
ld a,$80
out ($bf),a ; R0: legacy graphics
xor a
out ($bf),a
ld a,$81
out ($bf),a ; R1: display/VDP interrupt disabled
out ($c0),a ; controller reads now use joystick mode
call init_picture
main:
call wait_frame
ld hl,($6002)
inc hl
ld ($6002),hl
include "../sample.inc"
ld a,($6001)
ld a,b
nop
jr z,no_action
ld a,($6000)
inc a
ld ($6000),a
call trigger_sound
no_action:
ld a,b
ld ($6001),a
call draw_picture
call service_sound
jp main
; AF clobbered. This caller exclusively owns the VDP status/command latch.
wait_frame:
in a,($bf)
wait_new:
in a,($bf)
and $80
jr z,wait_new
ret
unused_rst:
ret
nmi_return:
retn
include "../picture.inc"
include "../sound.inc"
defs $ffff-$+1,0
The complete caller
Working setup and integration
; Original capability caller: owns VDP, one audio channel and declared RAM.
VDP_DATA equ $be
VDP_CTRL equ $bf
TURNS equ $6000
SOUND_TICKS equ $6005
; ColecoVision BIOS-started cartridge. Left fire commits one untimed turn.
; Owns VDP/controller mode, RAM $6000-$6004 and stack below $63F0.
org $8000
db $55,$aa ; skip BIOS title, enter start vector
dw 0,0,0,0,start
jp unused_rst
jp unused_rst
jp unused_rst
jp unused_rst
jp unused_rst
jp unused_rst
jp unused_rst
jp nmi_return
start:
di
ld sp,$63f0
xor a
ld ($6000),a
ld ($6001),a
ld ($6002),a
ld ($6003),a
in a,($bf) ; reset control latch and clear pending flags
xor a
out ($bf),a
ld a,$80
out ($bf),a ; R0: legacy graphics
xor a
out ($bf),a
ld a,$81
out ($bf),a ; R1: display/VDP interrupt disabled
out ($c0),a ; controller reads now use joystick mode
call init_picture
main:
call wait_frame
ld hl,($6002)
inc hl
ld ($6002),hl
include "sample.inc"
ld a,($6001)
cpl
and b
jr z,no_action
ld a,($6000)
inc a
ld ($6000),a
call trigger_sound
no_action:
ld a,b
ld ($6001),a
call draw_picture
call service_sound
jp main
; AF clobbered. This caller exclusively owns the VDP status/command latch.
wait_frame:
in a,($bf)
wait_new:
in a,($bf)
and $80
jr z,wait_new
ret
unused_rst:
ret
nmi_return:
retn
include "picture.inc"
include "sound.inc"
defs $ffff-$+1,0For more responsive controls, compare polling, buffering and press edges in input choices.
Verification and sources
Target: NTSC ColecoVision, recorded BIOS, title-skipping 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.
Coleco, ColecoVision technical documentation: controller mode ports and cartridge vectors; Texas Instruments SN76489A data manual: latch/data writes.