Build the complete caller with make -C atari-7800/patterns/assembly/input/read-action. Press 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
lda $000c
and #$80
eor #$80
sta currentLegacy INPT4 at $0C uses active-low bit 7; the inline sampler stores 0/$80 in current=$1904. It changes A/flags and does not read the second independent controller button.
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=$1901, current=$1904; turn=$1900, field clock=$1902/$1903. 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
Add the second controller button only after handling its distinct wiring. Do not copy the 5200 bit-0 trigger mask.
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
; Capability caller: original input/frame service, explicit drawing and sound.
; atari-7800: untimed turns from a new fire press, with independent field work.
; Owns display/input and $1900-$1904; stack is page 1. IRQ disabled.
turns = $1900
previous = $1901
clock_ticks = $1902
current = $1904
* = $4000
start:
sei
cld
ldx #$ff
txs
lda #7
sta $01 ; enter/lock 7800 mode; BIOS authentication is external
lda #0
sta $3c ; MARIA DMA off during pointer setup
lda #>dll
sta $2c
lda #<dll
sta $30
jsr init_picture
wait_enable:
bit $28 ; enable DMA only during VBlank
bpl wait_enable
lda #$40
sta $3c ; 160A, empty display lists, background visible
lda #0
sta turns
sta previous
sta clock_ticks
sta clock_ticks+1
main:
jsr wait_frame
inc clock_ticks
bne sample
inc clock_ticks+1
sample:
!source "../sample.inc"
lda current
nop
nop
nop
nop
beq no_action
inc turns
jsr trigger_sound
no_action:
lda current
sta previous
jsr draw_picture
jsr service_sound
jmp main
wait_frame:
bit $28 ; MSTAT bit 7: leave any current VBlank
bmi wait_frame
wait_new:
bit $28
bpl wait_new
rts
irq:
rti
!source "../picture.inc"
!source "../sound.inc"
!align 255,0
dll:
!for i,1,16 { !byte $0f,>$1940,<$1940 }
!align 255,0
graphics:
!fill 4096,$ff
!fill $fffa-*, $ff
!word irq,start,irq
The complete caller
Working setup and integration
; Capability caller: original input/frame service, explicit drawing and sound.
; atari-7800: untimed turns from a new fire press, with independent field work.
; Owns display/input and $1900-$1904; stack is page 1. IRQ disabled.
turns = $1900
previous = $1901
clock_ticks = $1902
current = $1904
* = $4000
start:
sei
cld
ldx #$ff
txs
lda #7
sta $01 ; enter/lock 7800 mode; BIOS authentication is external
lda #0
sta $3c ; MARIA DMA off during pointer setup
lda #>dll
sta $2c
lda #<dll
sta $30
jsr init_picture
wait_enable:
bit $28 ; enable DMA only during VBlank
bpl wait_enable
lda #$40
sta $3c ; 160A, empty display lists, background visible
lda #0
sta turns
sta previous
sta clock_ticks
sta clock_ticks+1
main:
jsr wait_frame
inc clock_ticks
bne sample
inc clock_ticks+1
sample:
!source "sample.inc"
lda previous
eor #$ff
and current
beq no_action
inc turns
jsr trigger_sound
no_action:
lda current
sta previous
jsr draw_picture
jsr service_sound
jmp main
wait_frame:
bit $28 ; MSTAT bit 7: leave any current VBlank
bmi wait_frame
wait_new:
bit $28
bpl wait_new
rts
irq:
rti
!source "picture.inc"
!source "sound.inc"
!align 255,0
dll:
!for i,1,16 { !byte $0f,>$1940,<$1940 }
!align 255,0
graphics:
!fill 4096,$ff
!fill $fffa-*, $ff
!word irq,start,irqFor more responsive controls, compare polling, buffering and press edges in input choices.
Verification and sources
Target: NTSC 48 KiB cartridge, reset without BIOS authentication. 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.
Atari, 7800 Software Guide: controller/TIA interface, MARIA/MSTAT and cartridge reset; Stella audio register definitions.