In earlier lessons we relied on delays and loops to control program flow and speed.
While this works for simple programs, it is not suitable for games.
Games require:
On many systems this is achieved using hardware interrupts.
On the Tatung Einstein, however, reliable game timing requires a slightly different approach.
This lesson explains:
For a game programmer, an interrupt is not about background processes or multitasking.
In game terms, an interrupt simply means:
“A fixed moment in time when the game advances by one step.”
Ideally, this happens once per video frame.
That fixed step is often called:
The important idea is fixed timing, not how the signal is generated.
If timing is controlled using delay loops:
Games solve this by separating:
Once time is separated from logic, game speed becomes stable and predictable.
The Einstein’s video chip does generate a vertical blank interrupt every frame (about 50 Hz).
However:
So:
You cannot reliably hook a per frame VDP interrupt for games.
The Einstein includes a Zilog CTC, and the documentation describes IM 2 interrupt handling.
In theory this could be used for frame timing.
In practice:
This makes IM 2 unsuitable for arcade game timing.
Because reliable per frame interrupts are not available, Einstein games use a different but very effective technique:
Synchronise the main loop to the video frame using VDP polling.
This gives:
From a game logic point of view, this behaves exactly like an interrupt.
Key concept:
All game timing is based on counting frames.
There are no special hardware game timers.
Instead, timers are:
One frame = one game tick.
The Einstein VDP status register contains a flag that changes once per frame.
By polling this flag, we can wait for the next vertical blank period.
This gives us perfect frame synchronisation.
The following program:
* every 200 frames (about 4 seconds)
ORG 256
; -----
; START
; -----
START:
LD A,0
LD (FRAME_COUNTER),A
; --------------
; MAIN GAME LOOP
; --------------
MAIN:
CALL WAIT_FOR_FRAME ; WAIT UNTIL ONE VIDEO FRAME HAS PASSED
CALL GAME_TICK ; UPDATE GAME TIMERS ONCE
JR MAIN
; ----------------------
; WAIT FOR ONE VDP FRAME
; ----------------------
WAIT_FOR_FRAME:
WAIT_LOOP:
IN A,(9) ; READ VDP STATUS PORT
BIT 7,A ; TEST VERTICAL BLANK FLAG
JR Z,WAIT_LOOP ; LOOP UNTIL FLAG BECOMES SET
IN A,(9) ; CLEAR THE FLAG
RET
; -------------------------
; GAME TIMER UPDATE (50 HZ)
; -------------------------
GAME_TICK:
LD HL,FRAME_COUNTER
INC (HL)
LD A,(HL)
CP 200 ; 200 FRAMES ≈ 4 SECONDS
RET NZ
LD A,0
LD (HL),A
LD A,42 ; ASCII CODE FOR '*'
RST 8
DEFB 158
RET
; -------------
; FRAME COUNTER
; -------------
FRAME_COUNTER:
DB 0
The frame counter acts as the master game clock.
Example:
The clock never stops.
Everything else is timed relative to it.
Each game object gets its own timer:
PlayerAnimTimer DB 0
EnemyAnimTimer DB 0
BulletTimer DB 0
Inside game_tick:
INC (PlayerAnimTimer)
INC (EnemyAnimTimer)
INC (BulletTimer)
Each timer now counts frames independently.
The main loop reacts to timers reaching certain values.
Example: animate player every 8 frames.
LD A,(PlayerAnimTimer)
CP 8
JR C,NoAnim
XOR A
LD (PlayerAnimTimer),A
CALL AdvancePlayerAnimation
NoAnim:
This guarantees:
By choosing different limits:
Timer Frames Result
Player animation 8 Smooth walk
Enemy animation 16 Slower movement
Bullet movement 2 Fast motion
Game clock 50 One second
All updated once per frame.
Conceptually:
game_tick is your interruptPhysically:
This is safer and more reliable than hardware interrupts on the Einstein.
Good frame tick tasks:
Bad frame tick tasks:
Keep it deterministic and fast.
Think of the system like this:
Wait for frame
Update timers
Update game state
Draw frame
Repeat
Once this structure is in place: