A course step measures progress through the storm. It does not measure elapsed time once we allow different numbers of steps per update. We will add a separate clock before adding boost.
frames remains the interrupt handler’s byte. This lesson’s program adds three run variables: the bytes last_frame and frame_delta, and the word elapsed. At each accepted update, the main loop compares frames with last_frame, records the difference in frame_delta, and adds that difference to elapsed. The course word ticks still increments once per update. The time is not printed yet: the inspector reads it from RAM, and the HUD still shows only the score.
Source · view and edit
Companion file: assets.inc
The include line reads this editable data when you assemble. Keep it beside your source for a local build.
Edit the source, then assemble.
Edits are saved in this browser. Keep a downloaded copy of work you want to keep.
Focus the Spectrum screen to use the keyboard, or hold these buttons. Assemble again for a fresh machine.
Run the program to inspect it. Editing source does not change the running machine.
frames- —
frame_delta- —
last_frame- —
elapsed- —
ticks- —
Most updates have frame_delta=2. A run can begin between alternate-frame opportunities, so its first difference may be one. This difference is measured; it is not assumed from the intended update rate.
Subtraction across a byte boundary
Suppose last_frame is 254 and the current frame byte is 0. Byte subtraction 0-254 keeps the low eight bits of the result, which is 2. That is the number of elapsed frames across this wrap.
This only works if fewer than 256 frames pass between samples. A difference of two cannot distinguish two frames from 258. Our update loop is much more frequent than that; a long pause outside this loop would require a wider clock.
; Fixed 25 Hz updates: rendering density must not set movement speed.
ld a,(frames)
ld c,a
ld a,(last_frame)
ld b,a
ld a,c
sub b
ld (frame_delta),a
ld a,c
ld (last_frame),a
ld hl,(elapsed)
ld a,(frame_delta)
ld e,a
ld d,0
add hl,de
ld (elapsed),hlAdd a byte to a word
The frame difference goes into E and D is set to zero, making a sixteen-bit DE value. ld hl,(elapsed) reads the existing low and high bytes into HL. add hl,de performs a word addition, carrying from the low byte into the high byte when needed. ld (elapsed),hl writes both result bytes back.
This is why elapsed can pass 255 while the frame byte wraps. It still has a limit: 65535 PAL frames is about 21 minutes 51 seconds. Our fixed course finishes far earlier; this is a short-run timer, not an unlimited clock.
An overloaded update takes four frames but advances ticks once. How much should elapsed increase?
Show the explanation
Four. Elapsed measures time consumed, including work that was too slow. Adding a constant two would hide the missed update opportunity and make a slow implementation report a falsely fast time.
The three new variables sit inside the run block, so new_game clears elapsed with the rest of the run. It now ends with ld a,(frames) and ld (last_frame),a, recording the current frame byte as the baseline once the run is set up. It does not need to restart the hardware clock. The result screen freezes the run’s elapsed value, while the interrupt’s frames byte keeps advancing.
Watch a result for a few seconds and compare the two inspectors. One stops because the run is over; the other continues because interrupts are still enabled. The same separation lets R begin a new run from a fresh baseline without stopping or resetting the interrupt.
Sources
Zilog, Z80 CPU User Manual, UM008011-0816, “Interrupt Enable/Disable”, “Mode 2” and the DI, EI, HALT and RETI instruction entries. The frame-rate experiments here target the stock 48K PAL Spectrum.
Run it here
This unit's program, built for you.
- Program
- meteor-storm.tap
- Machine
- ZX Spectrum 48K (PAL)