Core Dump - Work In Progress
August 3, 1998 : VDP coding trick
Aleph map size
The game is looking rather cool at the moment. There's more enemies to blast than there were in Black Cyclon, even.
I did an "estimate size" analysis on the maps. I'm rather surprised at the outcome. I just counted the maps (block location data), minus the headers, and got this number of blocks that are in the total map. Then I divided that number by (32*24), which is the number of blocks that would fill a 256 x 192 pixel screen. So that ends me up with the total number of screens, right?
But that would imply I've got far over 500 of screens' worth of playing area at the moment. Consider that you will see all of these screens more than once, and you've got a lot of walking to do! The only screens you'll not be able to visit all the time are those outside of Aleph, so you can imagine this space station is quite big.
But then I've got to add that because of the speed of the thing it doesn't actually feel like that much screens. Oh well.
A coding trick
As I've noticed that many people are happily coding away at games, I will explain a trick that speeds up games that have many seperate copies, like Core Dump.
For such a game that uses small blocks for the background and the sprites, extremely large amounts of copies are needed for a single frame. So one of the first priorities is to make sure your copy commands are as quick as they can be.
First, the usual tricks. I suspect everybody knows these by now:
So, the commands ends up like this (in pseudo-code, and assuming your interrupt routine doesn't mess up the VDP status too much)
VDPcommand: di ld a,32 ; first command argument in this case out (099h),a ld a,IndirectRegister+128 out (099h),a ei VDPcommLoop: ld a,2 call ReadStatusRegister bit [the_bit_for_CommandExecute],a jr c,VDPcommLoop ld c,09bh ; indirect register write di outi outi ... .. outi ; fifteen, usually ei ret
(Yes, I am rather sloppy here, but I assume you can write a copy like
that with your head in a large bag.)
ReadStatusRegister: di out (099h),a ld a,08fh out (099h),a in a,(099h) push af xor a out (099h),a ld a,08fh ei out (099h),a pop af ret
Yuk! What an ugly bit of code that is. But you see, it has to be done like that because of the interrupt handlers.
This is a bit of a historical design decision. On an MSX1 there is only one statusregister. You could get the value of it by doing just in a,(099h). An MSX2 has multiple registers. This is solved by writing the number of the status register to register 15. Then, reading port #99 gives the correct result. (Now comes the ugly bit) MSX1-like software interrupts (including the one in your MSX2-bios) just read port #99. They simply assume that statusregister 0 is selected. So, you can only read other statusregister with the interrupts disabled, and you then have to set register 15 back to 0 afterwards. That's what the code did.
So... just build a different interrupt, and use statusregister 2 as the default one!
Interrupt: di push af xor a out (099h),a ld a,08fh out (099h),a in a,(099h) ; is required in any such interrupt ld a,2 out (099h),a ld a,08fh out (099h),a push bc push de push hl ... [more stuff]
Now reading a statusregister becomes:
ReadStatusRegister: di out (099h),a ld a,08fh out (099h),a in a,(099h) push af ld a,2 ; this has changed! out (099h),a ld a,08fh ei out (099h),a pop af ret
But the time-gain is in the copy routine, because it changes
ld a,2 call ReadStatusRegisterinto the much faster
This technique is used in Core Dump. Actually, I alternate two interrupt modes... but that is a different story.
Room for improvement
Of course there is also the alternative of a "correct" interrupt, i.e. one that enables the coder to select the default status register. It's actually bloody easy, come to think of it, and we only need one variable, DefaultReg.
Interrupt: di push af xor a out (099h),a ld a,08fh out (099h),a in a,(099h) ld a,(DefaultReg) out (099h),a ld a,08fh out (099h),a push bc ... ... bla bla
Then, we need a procedure to set a new default register:
SetDefault: di ld (DefaultReg),a out (099h),a ld a,08fh ei out (099h),a ret
Now, any register can be selected by the procedure, and after that in a,(099h) can be used to read it. Funny, I should think, because it's very quick indeed.
You'll have to beware, though, because you have to adjust the 0038h interrupt vector with ram in page 0. You can't use the bios from that point. To go the the bios, you'll have to make register 0 the default one again, and then you can put the bios back into page 0. Other than that it works like a charm.