-----Original Message-----
From: Simon Cooke <[EMAIL PROTECTED]>
To: [EMAIL PROTECTED] <[EMAIL PROTECTED]>
Date: 21 February 1999 07:38
Subject: Re: SAM FAQ Version 2


>Unorthodox frame sync code? Pray tell! I can only think of a couple of
>ways of doing it...

OK.  Apologies to everyone because this is a quite long.  Also, advanced
apologies if it's 'egg sucking' territory to anyone.

God, that sounds patronising...  Sorry.  :-)

Anyway, deep breath!  Here we go:

First, a little description of how SAM Defender works will later highlight my
need to use unorthodox frame sync code.

Defenders' internals are almost exclusively 'stack based'.  All of the graphic
routines use the stack for dumping/erasing.  The main sprite dumpers are
all hard-coded and use an interleaved format.  This format removes the need
for an ADD 128 or SUB 128 to the screen address when moving to the next
screen line, reducing it to a simple INC or DEC of the high-byte with the ADD
or SUB required only when switching the dump direction at the top of the
sprite.

All of the main data structures inside the program are again manipulated using
the stack.  One of the main reasons for this is the fact that all of the
movement
within the game is fractional, using 16-bit fixed-point allows easy programming
of inertia and gravity effects.  Also, the width of the 'world' is some 2048
pixels,
so 8-bit just doesn't cut it!  These 16-bit data lists are much faster to
manipulate
using the stack, especially when all of the usual registers are tied up leaving
only
the fat and lethargic IX or IY pairs, which are to be avoided if performance is
the
key factor.

To simplify, the stack is used almost exclusively because it's so efficient and
fast
at shunting around word-sized chunks of data.  There's an *awful* amount of this
being shunted around, especially when you have to dump/erase a 256-pixel wide
landscape, manipulate the data lists for as many as 40 sprites, dump/erase any
of
these sprites that happen to be on the 'window' into the 2048-pixel wide
world...

All this must happen at a constant (or as close as) 50-fps for the game to be
anywhere near the quality of the arcade original.  So, the good old stack gives
a
much needed boost in performance.  Note the use of 'as close as' above?  This
becomes important later.

Because data lists are manipulated using the stack, and these lists contain data
that
is used continuously by the program, any corruption would spell disaster.
Corruption
would occur if an interrupt occurred, for example.

SAM Defender runs with the interrupts in a permanently disabled state to prevent
such
potential corruption.  From a performance point of view it wouldn't be
economical to DI,
EI all over the place because stack manipulation is occurring almost
continuously during
the game.

So, back to the question of frame syncing.  Usually you can frame sync on
machines
like SAM by having a HALT instruction at the top of your main loop.  Trouble is,
HALT
requires the interrupts to be on.  Defender runs in a permanent DI state, as
mentioned.
OK, so I could have EI, HALT but that would have meant setting up an ISR that
simply
RETs.  Not worth the bother.  I'm not complicating the issue here with the
potential for
other types of interrupt to occur...

Originally I was frame syncing by reading the FRAME INT bit on port 249 at the
top of
the main loop.  This worked fine, until the game 'missed' a frame, at which time
the
main loop will actually catch the next frame, causing a drop down to 25-fps.
Everything
switching to half-speed for a time, including the sound, then back to full-speed
is just
plain ugly!

If you watch the arcade original you'll notice that it misses frames too.  In
fact, it misses
more frames than my SAM version does!  But, there's one crucial difference.  It
doesn't
drop to half-speed but rather jumps/jerks a bit, with very little slowdown.
How?

I concluded that the programmer was able to detect the fact that the game had
missed a
frame and simply didn't bother waiting for it when the program got back to the
top of the
main loop.  He simply allowed the program to continue as if it *had* caught the
last frame,
which technically it did but was a little late!

SAM Defender can never miss any more than *one* frame in a given cycle of the
main loop.
So, knowing you've missed a frame, you can simply continue processing as if you
*had*
waited for it.  In other words, on a missed frame remove the frame wait at the
top of the
main loop.

The benefit here is that the program is allowed to run *full belt* for the next
cycle, and for
any cycles after that where a frame is missed, until everything falls back in
sync.

The on-screen effect is identical to the arcade original, a slight jumpiness and
a very small
slowdown which is proportional to the amount of 'extra' work, beyond a normal
frame, that
the program has to do.  No more 50% speed drop in game motion, and no audio
quality
loss.  Much more pleasing!  It also let me allow the program more objects in the
world and
more sprites on-screen!  ;-)

How does it know when its missed a frame?  Simple, I hear you cry, use the LINE
INTs!
Yes, that's one way.  But I can't allow interrupts, so I can't use the LINE INTs
to set a
'missed frame' flag!  ;-)

I tackled it like this:

Crucial to the success of this method of frame syncing is the proper functioning
of the
'HPEN' (vertical lightpen register, port 504) when no lightpen or similar device
is
connected.  Defender includes code in its initialisation that detects if you
have a lightpen
connected.  If you have, it informs you that it must be removed for the game to
function
correctly.

Reading the HPEN port produces a value that reflects the current vertical (Y)
raster
position.  It runs 0 - 191 for the main screen area and 192 when the raster is
in the
border area.

The frame sync code keeps two variables:

1.  A flag to signal that the last frame was missed.  "missed_last"
2.  The raster position after processing 50%.           "half_way"

"half_way" is set thus:  After Defender has processed most of its more time
consuming tasks, a read is made of HPEN and saved in "half_way".  This is
approximately 50% of all the processing Defender has to do in one frame.

At the start of the main loop I check the "missed_last" flag, if it's false
(zero) I
simply compare the "half_way" variable to 192.  If this compare is true then the
program managed to do all of its processing before the raster left the border
area
at the top of the screen.  In this case it simply waits for the HPEN contents to
change from 192 to <>192 and then back to 192 again.  Similar to a 'normal'
vertical blank routine.

Note, the raster *cannot* reach the start of the border area at the bottom of
the
screen after Defender has processed just 50% of its tasks.  However, programs
with the potential to miss more than one frame in a row might.  This routine
wouldn't work correctly in these cases.

If "missed_last" was true (not zero), signalling a known missed frame, or
"missed_last" was false but "half_way" >=0 and <=191, signalling a potential
missed frame, the code reads the HPEN port.

If HPEN = 192 (back in the border again) then its a missed frame, the
"missed_last"
variable is set to true and a jump is made into the main game processing,
skipping
the frame-sync wait.

Else, HPEN is compared to "half_way" and if it's value is <= "half_way" the
raster
has wrapped through the border and is back on its way down the screen to the
"half_way" point again, which is a missed frame.  In this case, again,
"missed_last"
is set to true and a jump is made into the main game processing, skipping the
frame-sync wait.

If HPEN > "half_way" then the program is back on track and the "missed_last"
flag is cleared and a 'normal' frame-sync (a wait for HPEN = 192) is performed
before entering the main game processing loop.

If you throw some test values at the algorithm you'll see how it works.  After a
missed frame, but the program is back in sync again, it'll take at least the
next
frame to settle down.  This isn't noticeable however!  ;-)

There are probably other ways to achieve the same result.  This way worked
great for Defender on a *real* SAM.  Simcoupe doesn't seem to like it though!

Phew...  Lots of writing for a routine that's only 24 lines long!!

Hurry with that Win32 port, Si.  What Simcoupe desperately needs is quality
sound emulation and a 50Hz refresh, if that's possible under Windows???...

Chris.

Reply via email to