> Simon Owen wrote: > Simon Cooke wrote: > > Interesting... Defender uses HPEN/LPEN... any idea how it > > uses it? > > It uses HPEN monitoring to track whether it's running late with the current > frame. I believe it still expects to be over the main screen area after > completing certain tasks, and if it's into the border it runs straight into > the next frame without waiting for the frame interrupt to be signalled in > the status port. The game uses the stack for lots of the drawing > code/lists, so runs with interrupts disabled at all times [you can confirm > that in the SimCoupe debugger by pressing U for 'execute until', and > entering 'ei' for the expression - the breakpoint will trigger next time > interrupts are enabled, which never happens].
Simon's explanation is correct. I will expand (from memory!) why I used this method: As we all know, the SAM is *seriously* underpowered. To ensure Defender ran at a constant (99.9% of the time!) 50-fps, all available horsepower needed to be squeezed from the old Z80! Often the fastest way to shunt data around is via the stack, so I used the stack to dump/erase sprites and also used a stack-based linked list for the world entities. The landscape also uses a stack-based dumper, this pops off a value which is added to the current landscape pixel position, moving the position either up, down or up/right, down/right. The mini-landscape used in the scanner is processed in a similar way. Even the 16 background stars are processed using a stack-based routine! Couple this with plenty of code unrolling, in-lining of graphics data and also screen-line interleaving of sprite dumping the game holds on to the magic 50-fps. With so much stack activity occurring, often in crucial data areas, any unexpected interrupts would be a disaster. So Defender runs a constant DI state. This is fine, but introduces the problem of frame-syncing via the v-blank interrupt. Because of the DI state it is not possible for Defender to trap the v-blank interrupt. Instead, a simple polling loop on the status port has to be used. Again, this is fine if you can complete all in-game processing before the next v-blank. Defender does (just!) for 99.9% of the time. This is where the problem lies. If you miss the v-blank, but you still wait in the status polling loop after all in-game processing, you effectively "see" the next v-blank and not the one you really wanted to see, because you missed it! This has the effect of dropping the game to 25-fps visually, and halves the speed of all sound effects. This is so very obvious to both eye and ear!!! Unacceptable! As Defender only just misses the frame v-blank when things are hectic on-screen it was totally unforgivable to allow the game to stutter to half its speed, ruining both the game and its playability. The solution was not to wait if we had missed a v-blank, but simply continue processing the next game-loop as if we had just seen a v-blank. This is simple if you can set a flag on a v-blank interrupt. In Defender's case this is not possible, so an alternative method for detecting missed v-blanks was used. Enter HPEN! Put simply, I read the HPEN value after processing approximately 50% of the in-game functions (50% *never* misses v-blank) and then read it again after all in-game processing is done. Then a few calculations determine if we missed the last v-blank. If so, then don't wait for a v-blank and simply blast into the next game loop. The effect is a slight visual stutter on missed frames, and no real change in the sound effects. 100% preferable to the 25-fps visual drop and audible slowdown of all effects! In fact, the visual stutter is almost exactly the same as the real Defender machine when it, too, misses frames. The exception being that SAM Defender doesn't bother "hyperspacing" entities off-screen on missed frames, which the real Defender does when it trys to claw back some precious CPU time!! ;-) Job done! :-) Chris.

