The background changes as updates advance. Hold Fire on physical joystick port 2 and a deliberately long CPU loop slows those changes. Release it and the caller returns to one update per display frame in the tested scene.
Use the wait in your project
Call wait_frame once per update with A5 unchanged. D6 and condition codes change; other registers survive. It clears the pending VERTB request, then polls for a newly asserted one. No interrupt handler may consume that same request. Clearing old pending work is deliberate: this helper waits for a new boundary rather than treating a stale request as another update.
; A5=$DFF000, caller owns custom VERTB request and disables its IRQ consumer.
; Drop pending work, then await a newly asserted VERTB request.
; D6/CCR change; other registers survive. This is a fixed-step wait, not a timer.
wait_frame:
move.w #$0020,$09c(a5)
wait_vertical:
move.w $01e(a5),d6
and.w #$0020,d6
beq.s wait_vertical
rtsStart the complete example
Boot the ADF on the tested A500 OCS PAL/Kickstart 1.3 configuration. The caller disables custom interrupts and DMA, sets A5 to $DFF000, configures the Fire input and owns the background colour. It takes the machine over; it does not restore Workbench. Reset to leave.
With Asm198x available, keep the sample folders together and run:
make -C commodore-amiga/patterns/assembly/framework/game-loopThe Amiga Makefile also needs Build198x. Open the generated file under build using the machine’s normal loading route.
The loop keeps four jobs visible: wait, read input, update state, draw. expensive_work is an optional CPU workload to expose missed deadlines; remove it when building your own game. The changing colour or digit is a small observable result, not a rendering engine.
; A500 OCS PAL, KS1.3, boot ADF. Physical port 2 Fire adds CPU work.
; Takes over custom IRQ/DMA and background colour; chip-memory executable.
; Reuses the input reader from the verified input/sound caller. Reset to leave.
section code,code_c
start:
lea $dff000,a5
move.w #$7fff,$09a(a5)
move.w #$7fff,$09c(a5)
move.w #$7fff,$096(a5)
bclr #7,$bfe201
clr.w updates
clr.w slow
move.w #$cafe,ready
main_loop:
bsr wait_frame
bsr read_controls
move.w controls,d0
and.w #$0010,d0
move.w d0,slow
bsr update_game
bsr draw_screen
bsr expensive_work
frame_done:
bra.s main_loop
update_game:
addq.w #1,updates
rts
draw_screen:
move.w updates,d0
lsr.w #3,d0
and.w #7,d0
lsl.w #8,d0
or.w #$0030,d0
move.w d0,$180(a5)
rts
expensive_work:
tst.w slow
beq.s work_done
move.w #60000,d0
work_inner:
dbra d0,work_inner
work_done:
rts
signature: dc.b "198XLOOPAMIGA!",0,0,0
even
ready: dc.w 0
updates: dc.w 0
slow: dc.w 0
include "frame.inc"
include "../input-to-sound/input.inc"
program_end:What happens when work runs late
The request is a latch, not a count of every missed frame. Slow work reduces update frequency and the loop does not catch up. This is one hardware-owning approach; Flock also explains a two-phase beam-position wait. Neither is an AmigaDOS-friendly scheduling API. Other models, regions, display arrangements and DMA loads need their own qualification.
Check this combination
Code status: assembled and emulator-executed. A500 OCS PAL, Kickstart 1.3. The recorded checks identify sources, firmware and observer. In three 30-frame windows the caller made 30 normal, 5 deliberately slowed and 30 recovered updates. An unpaced negative control is rejected by the same rate guard. This is configuration-specific emulator evidence, not an original-hardware or whole-emulator claim.
The loop comparison puts the ownership choices side by side. Each implementation keeps its own timing and resource contract.