Give an accepted action a sound. The same caller changes its picture and triggers this effect; a second version performs the action with the trigger removed. Listen to both.
Working effect
Trigger omitted
Take the effect
The reusable effect
PSG equ $ff
; Pulse on SN76489 tone0, eight serviced frames. Other channels are muted here.
trigger_sound:
push af
ld a,$80
out (PSG),a
ld a,$10
out (PSG),a
ld a,$90
out (PSG),a
ld a,8
ld (SOUND_TICKS),a
pop af
ret
service_sound:
ld a,(SOUND_TICKS)
or a
ret z
dec a
ld (SOUND_TICKS),a
ret nz
ld a,$9f
out (PSG),a
retSN76489 tone 0 at port $FF, duration byte $6005. Trigger preserves AF and B; service changes AF. Picture initialisation mutes all channels, so the complete caller takes audio ownership.
Include sound.inc once with the caller’s constants and duration byte. Initialise the duration byte to zero before the first service call. Call trigger_sound for an accepted action, then call service_sound exactly once per accepted display update. The eighth service call mutes the effect. Because the supplied caller services in the triggering iteration too, this is a count of service calls, not eight complete elapsed frame intervals. Triggering again restarts the duration; there is no queue.
The complete initialiser also mutes other channels. To reuse just this effect beside music, agree channel ownership and remove unrelated global setup only after providing a replacement. Device latches, power, routing and master volume may affect more than one voice.
Change it and recognise a mistake
Change the PSG’s ten-bit tone divisor or attenuation. Latched register writes must remain ordered; concurrent sound writers need arbitration.
The omitted-trigger control still accepts the action but fails the same audio guard as the working caller. Keyboard clicks or other machine output are not enough: the BASIC checks compare against a separate rejected-command recording.
Deliberately omitted sound trigger
; 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
nop
nop
nop
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,0The working implementation above repairs that omission. If an effect instead drones on, check the mute/service path and that its state is initialised. Servicing twice makes a counted effect shorter; servicing only after input can leave it running while the player waits. Those are lifecycle decisions to test when adapting it.
Run the caller
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.

Where setup, trigger and service belong
; 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,0Compare blocking, serviced and queued sound when choosing the next step.
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.