Saturday, 24 December 2022

A major milestone - on Xmas Eve no less!

I have finally reached a major milestone in the project; with the debugging of the high score entry screen, the transcoding from Z80 to 68K is essentially complete! 😀

High Score entry screen complete to round out the transcode!

I do know of a couple of minor bugs in the transcode, and lack of proper support for flipping of 2x2-tile sprites, but I've been focusing on getting the remainder of the code RE'd and converted to 68K.

Next task is to go back through the listing for both side-by-side, add or clarify any missing annotations, fix typos in labels etc, and identify and document any unused code. I also need to tidy up the memory variable area(s) and ensure they're initialised (to zero) in the right places. It's a bit of a dog's breakfast in the original code as there are so many different areas of shared RAM.

Then it'll be onto Neo Geo optimisation and getting it running at 100% on real hardware, with little or no disappearing (Neo Geo) sprites (due to scanline limit). Somewhat fortuitously, visible display masks and the foreground tile layer are the first to disappear, so at least the 'sprites' won't be affected. But of course ideally there will be no disappearing (Neo Geo) sprites at all!

I'm also working with another developer to get this running on the Amiga! Once the ball starts rolling I'm hoping there won't be a huge amount of effort required; it's basically 'just' replacing the Neo Geo-specific code with Amiga-specific code! Having said that, there's currently about 1,000 lines of Neo Geo-specific code... though in contrast about 7,000 lines of Xevious core code.

Due to circumstances beyond my control, I've had a little more time on holidays to work on Xevious, though - again - that may not be the case (hopefully) for the remainder of the holidays. Either way, I might take a bit of a break after finishing the transcode before I dive back into it.

Wednesday, 21 December 2022

Light at the end of the tunnel - and it's bright!

I've found some time - somewhat unexpectedly - to work on Xevious on holidays...

I finally finished the flying enemies getting destroyed, with a doozy of a bug that again, I had to sleep on. Turned out to be an inadvertant re-use of d7 in an inner loop subroutine call.

And that was the last piece of actual gameplay code that remained to be transcoded. Or at least so I thought... turns out during subsequent play testing that the Bacura don't destroy the Solvalou yet! Oops, I'll have to find that!

I've also started on what should be the last remaining piece of 'housekeeping' code - the high score entry screen. Somehow I've managed to introduce a glitch in the in-game high score update which I've never seen before?!? The high score entry isn't complete, and it's not quite right, but probably only a half-hour effort remains.

EDIT: Wait, I haven't explicitly coded or tested a 2P game yet...

Despite all of the above, the transcode is very, very close to being 'finished'. And by that I mean a first-pass, unoptimised transcode. The MAIN and SUB ROM code shouldn't require much in the way of optimisation; it's mainly the Neo Geo code. Other than that, it's a case of going back through the Z80 listing, reviewing the 68K code, and of course a lot of play-testing. There are some bugs, but it's basically completely playable as-is.

No eye-candy today. Aside from high score entry there's not really much new to show anyway. In my next post I should be announcing a major milestone as described above!

UPDATE: Transcode is complete! Buggy, but complete.

UPDATE #2: More work on the high score entry screen. Almost working...

Thursday, 15 December 2022

GAME OVER - for 2022 at least!

In the process of transcoding all the enemy AI logic, I ended up finishing off a few other housekeeping routines as the logic was embedded in them. Those routines comprised transition of one Solvalou to the next, and ending the game. Not quite 100% there as there's still no high score entry, but close now.

Game Over!

I also implemented the Solvalou being hit, exploding and ending the turn. A few bugs in the transcode made harder work of it than I'd hoped - including one I had to sleep on - but done and dusted now.

I also discovered that I haven't implemented the tile flip for 2x2 tile sprites completely. As the Solvalou explodes, the same sprites are displayed for a handful of frames and they are periodically flipped based on an asynchronous timer. I haven't tackled this just yet; I'll have to consider the best approach. I may even leave it until I work on optimising the Neo Geo code, as anything further I do now may end up getting re-written anyway.

I had actually looked at the destruction of the flying enemies first, but there was a snag I hadn't expected. It's just a technical detail concerning the difference in implementation between the Z80 and 68K. I just have to work out the optimal solution. Not a big deal, but finishing off the Solvalou seemed more straightforward. A moot point now as I have little option but to tackle it next.

EDIT: I forgot the Bacura! 😳

At a bit over 5,500 lines in the MAIN program, the 2:1 ratio of Z80 to 68K instructions is almost exactly right; I'm really getting down to the last few functions to be transcoded now. Annoying that I've pretty much run out of time to work on it this year, but sometimes you just have to take holidays. 😜

UPDATE: I managed to squeeze a bit more time on the transcode; some more housekeeping so that the high score is updated on-the-fly, and bonus Solvalou awarded according to dipswitch settings.

Bonus Solvalou at 20K and updated High Score

In the process I decided to byte-reverse the storage of all the scores in the game. It made the bonus calculations easier, without affecting the efficiency of any of the other routines.

Finally, I fixed a bug that has been in the transcode and eluding me for weeks now if not longer. It involves the score occasionally appearing as a solid black line, and after implementing high score update, it started occurring at the same time for the high score as well - so doubly annoying! A theory came to me out of the blue tonight - it turned out to be completely wrong, but by then I was determined to find the issue.

It was between the core and the Neo Geo layers; in the core's score display code (a BCD print routine) I was sending the tile code as a byte but the Neo Geo code was assuming the upper byte of the word was zero... which it was... most of the time... until it wasn't. So nice to have finally fixed it!

UPDATE: found a few free minutes to do the Bacura. I had to add support for double-height (only) sprites.

Bacura - the forgotten foe!

Wednesday, 14 December 2022

Xevious reveals all its secrets.

I've spent the last few sessions trying to finish off the RE and the missing pieces of the AI puzzle.

As a result, the SUB CPU ROM is now 100% complete. All the variables related to the game logic have been understood, named and the routines documented. I could probably spend an hour or so just tidying things up, adding a bit more annotation, but it's essentially done.

Correspondingly, the MAIN CPU ROM is all-but-complete. I haven't actually gone through the disassembly with a fine-tooth comb just yet, but the only missing annotations should be the miscellaneous housekeeping such as score and bonus updates. If I had to guess, I'd say it's 95% complete now.

As for what I've loosely referred to as 'AI' - for want of a better (more concise) term - I believe I've now RE'd all the logic that controls the difficulty of the game. It's not quite as complex as the advertisements suggested back in the day, and I wonder if plenty of other games don't have similar logic built in?!?

At the core of the logic is a couple of tables. First is a table that contains pairs denoting the number of flying enemies and an offset into a 2nd table. That second table is a list of enemy types in (roughly) increasing order of difficulty. So Toroids tend to appear first, with Zoshis and Brag Zakatos appearing later.

The map data contains a 'place holder' object that indexes into the 1st table; for example it may specify 3 Toroids. However there is another variable that is added to the index read from the 1st table, and used for the 2nd table - that variable is what I've named enemy_AI_level. This variable starts out at 0, and is incremented throughout the game - the effect being that the latter index gets larger and larger, causing more potent enemies to appear.

The enemy_AI_level is increased using a few different mechanisms:

  • decremented at the start of each new Solvalou according to the STARTING LIVES dipswitch setting
  • incremented explicitly in the map data accoring to the DIFFICULTY dipswitch setting
  • incremented explicitly in the map data depending on the current score
Incidentally, the enemy_AI_level is decreased by 2 each time a Zolbak is destroyed.

The adjustment depending on the current score is interesting; the amount of adjustment is calculated by dividing the current score (in thousands) by the number of Solvalou that have been used thus far. IOW the average score per Solvalou for the current game. The adjustment factor is (seemingly) equal to the thousands of points scored per Solvalou, with an upper limit of 16, so if you average more than 10,000* points per Solvalou - which is pretty easy for a seasoned player - then the game will play at effective maximum difficulty for that dipswitch setting.

[* Technically the score is represented in BCD, whilst the division operates as if it were in binary. 9,000 points on 1 Solvalou ($09/1) results in an increment of 9, whilst 10,000 points ($10/1) results in an increment of 16. Confusing, but there's still a monotonic increase.]

The other piece of RE I finished today was the Bacura, which operate with their own set of variables, not affected by the enemy_AI_level but rather explicit values in the map data. I'm yet to implement any of the Bacura logic in the transcode; that's next!

Once I finish the Bacura code and debug a few minor issues in the flying enemies, it'll be a case of implementing the destruction of both flying enemies and the Solvalou, and then tidying up all the remaining housekeeping functions. Probably a week or so there, but I'm fast running out of time for the year.

The final stage (before sound) is then optimising the Neo Geo code, and getting it running at 100% on my real AES hardware!

UPDATE: enemy_AI_level is reduced at the start of each Solvalou, not adjusted at the start of each area.

Monday, 12 December 2022

Come fly with me...

I've started on the flying enemies. As a direct result, the SUB CPU transcode is 99.99% complete. There's one small AI-related routine that I'll leave until I've RE'd the logic. Otherwise, the Toroid now appears!

The first AI-driven flying objects - Toroids

As I've mentioned before, the ground-based enemies are not affected by the adaptive AI; every enemy and even their firing frequency is fixed in the game, regardless of the DIFFICULTY dipswitch setting or how well the player is playing. It's the flying enemies that respond to these parameters.

I haven't RE'd all the AI logic just yet, but I do know roughly how the flying enemies are selected.

In addition to all the ground-based objects, the map data contains 'placeholder' objects which invoke different functions for the flying enemies. These include:

  • set the number and type of flying enemies explicitly
  • increment the AI level (using the DIFFICULTY dipswitch setting) and re-calculate the number and type of flying enemies
  • reset the number and type of flying enemies (stops new ones appearing)
The MAIN CPU ROM contains a table of flying enemy numbers and types in order of increasing difficulty; the 2nd function above uses the AI level to index into this table.

Once those variables have been set in the SUB CPU program as the map is being generated, the MAIN CPU program loops over a function that spawns flying enemies according to the current number and enemies type setting.

Obviously I had to implement these functions in order to have the Toroids appear, but I will note that I wasn't sure how/when those variables were being cleared. And when I was debugging the spawning, I actually couldn't find where they were being cleared - this meant that the routine was spawning flying enemies every single invocation - once every VBLANK!?!

So I went back to the arcade code and found exactly the same behaviour! And then it finally dawned on me... the number of flying enemies represents the number AT ANY ONE TIME, not the total number! So for example, 3 Toroids actually means 3 Toroids on-screen, and as soon as one is destroyed or leaves the screen, another one is spawned! FTR spawning an object that already exists has no effect.

I should have realised that a lot sooner, as there are a lot more Toroids than the handful that appear in the map data at the start of Area 1, for example. But I haven't been focusing on flying enemies for quite a number of weeks now.

Now that I understand this, I can proceed with transcoding more flying enemies.

FUN FACT: On the EASY dipswitch setting, the AI level increment used in the above function is set to 0. However it is actually incremented elsewhere, so it does get a little harder.

UPDATE: Toroid, Jara and (1 type of) Zoshi now complete. After some debugging, I'm confident now that the AI is levelling up correctly, as I see the same sequence and number of flying enemies as on the original. I'll have to ramp up the difficulty to HARDEST now to get all the enemies to appear sooner.

In the process I also did some miscellaneous code tidy-up - including the elimination of some duplicated inline code - and added the missing messages at the start of each Solvalou!

Didn't realise these messages were missing!

The MAIN program is around 5,000 lines now. If I'm using the metric that the 68K code is round 1/2 the number of lines of the Z80 code (~11,000 lines) it means I'm getting close to the end now. Aside from finishing off a handful of flying enemy object handlers, there's the destruction of said enemies (common routine), destruction of the Solvalou, and some miscellaneous housekeeping like zeroing variables, updating high scores, awarding bonuses based on score etc. And that's about it, except for sound!

UPDATE #2: I've been flying through the flying enemies and they're all transcoded now except for the Bacura (spinning mirrors) as they are handled in their own routine. There are a few minor bugs but they shouldn't be too difficult to fix. I will need a way of injecting specific enemies into the game at a certain point; that's what build options are for! I also need to document the behaviour of the different variants of certain enemies (Zoshi, Zakato, etc).

I've also done a little bit more of the AI RE in the process; I now know it keeps track of the number of Solvalou you have used in the game, and does some calculation with your score; I'm betting it's working out your average points per Solvalou - but yet to RE any further.

Saturday, 10 December 2022

The hidden beauty of the Brag Spario and another bug!

Finally finished the transcode of the Andor Genesis (mothership) and its preceding entourage; the Sheonites, the Garu Zakato (big exploding bomb) and Brag Spario (chaser bullets). Oh and the escaping Bragza!

The Andor Genesis descends,
and the sprite-per-scanline limit is just being exceeded

Interesting things happen when you're invincible. Whenever I've played the game, when awaiting the Garu Zakato I've always lurked at the bottom of the screen in order to more easily dodge the bullets. The Brag Spario approach the Solvalou but disappear off the bottom of the screen when you dodge them.

Whilst tesing the transcode, I ventured right up to the top of the screen before the Garu Zakato exploded! Ordinarily, you'd be destroyed by the radiating bullets almost immediately. But being invincible, the Brag Spario started orbiting the Solvalou like a display of orbital mechanics! It looked pretty cool, but I suspected a bug in the transcode. However when I fired up the arcade original and tried the same with invincibility - the same thing happened!

The Brag Spario should have featured more prominently in the game, to show off their fancy flying!

I also found a bug in the game, and again, I verified it on the arcade original! When destroying the Andor Genesis core, you score more than the allotted 4,000 points! See below, where I scored 4,010.

Can't really show the Brag Spario in a screen shot,
so instead a blew up the Andor Genesis core

It all comes down to how the Andor Genesis is represented in the object table. Last post I mentioned it comprises 9 'body' objects, 5 objects for the 4 gun ports and central core, and a 15th 'master' object which initialises the other objects and is used as the reference for the sprite coordinates etc.

Well, the way the bombing code works is that whenever your bomb explodes, it loops through the first 16 objects (ground-based objects) in the object table, destroying any object within range of the explosion. This is how you can destroy multiple objects, such as adjacent Lograms and Zolbaks, on the ground with single bomb.

So when you bomb the core, it first matches the core object (object #14) and you score the allotted 4,000 points. But then it continues and also matches the master object (object #15). However the master object never initialises the POINTS value (incidentally, there is no value that represents 0 points). So you are awarded the point value for the last object to occupy that slot.

For the Andor Genesis in Area 9, the last object was a Zolbak in Area 8, and so you are awarded an additional 200 points. When I was testing in the above screenshot, I was starting in Area 9; no object had been previously assigned to that slot, so POINTS was 0, which represents 10 points. You may recall, this is the same reason the Easter Egg at the start of the game is also worth 10 points.

I guess this means I'm finally up to the flying objects. The handlers for these tend to be a little more complex due to the flight patterns and the AI that affects their behaviour. Should be fun... but I might see if I can finish off the SUB CPU completely first...

Friday, 9 December 2022

The Mother of all Objects!

Finally debugged the Sheonites. Aside from more than the usual number of bugs in the transcode, I also updated the RE with more accurate subroutine descriptions as a result of my debugging. As it turns out, the left and right Sheonites are almost identical, except for the fact that when they 'combine' just before leaving, the left-hand Sheonite object is destroyed, and the right-hand Sheonite represents both as it shoots up the screen.

Sheonites done and debugged - the game
would never have been the same without them!

Having completed the Sheonites I moved on to the Andor Genesis (mothership). There was a bit of code in the SUB program that I had to transcode, and then work out which part(s) of the MAIN program handlers I had to implement first - the Andor Genesis comprises no less than 15 distinct objects in the object table.

In my RE up until now, I sped through the Andor Genesis handlers using 'place holder' names for all the parts. Now that I am implementing them one-by-one, I can actually back-annotate which part of the ship is handled by each object.

The ship comprises nine (9) 2x2-tile sprite 'body' objects (3x3), overlaid with 5 objects (sprites) for the turrets/reactors, and one 'master' object for the entity as a whole. I had to implement the latter object first, as all other objects reference that for state, position, etc. Thus far I have implemented the master (in part at least), the 9 'body' objects and one turret/reactor (bottom right).

Andor Genesis - partly complete

Xevious maintains a table of 64 objects - or rather a set of tables - that includes a table with the function handler address. As you'd expect, handlers (ordinarily) operate on the object with the same index as the handler is currently executing from. eg. A handler in slot #4 is operating on object #4.

The Andor Genesis, however, is unique in that a whole bunch of handlers - the 'body' handlers for example - don't reference the current object at all, but rather comprise hard-coded references to another object in the table. eg. The handler for object #1 (explicitly) modifies object #9 instead.

I can't say for certain why this has been coded this way, but it looks like they've had to reverse the order of objects in a hurry, and for some reason, they've chosen to do it this way. I should note that the lookup table for objects that comprise the Andor Genesis is actually in the SUB CPU ROM, so perhaps that has something to do with it?

Anyway, I've tried to remain true to the original code, and have preserved this aspect in the transcode. It did have the potential to mess things up a bit if the offending objects weren't all benign, but fortunately they don't call any common routines that expect the object pointer in A5 to be consistent with the index of the handler. Conversely, the not-so-benign turrets/reactors are in the right 'slot', so routines like firing at the Solvalou work properly without juggling registers.

I have seen the 96 sprites-per-line limit again with the Andor Genesis. In line with each turret there are actually 8 Neo Geo sprites, and that's without bullets. Line up the Solvalou to bomb a turret and that's another 4. Add a few bullets... that 20-sprite overhead is getting eaten up quickly.

I had the idea of turning off the middle of the tilemap layer during gameplay, as only the top 2 lines and bottom line or so is ever used during a game. A few more lines are used during attract mode, but it would at least give me another 12-30 sprites head-room...

Thursday, 8 December 2022

Much ado about nothing!?!

Even more RE as I wade through the code for the Garu Zakato (big exploding bomb) and the Sheonites (spinning pyramids) that precede it.

The Garu Zakato explodes with 16 radial bullets
and 4 Brag Spario that target the Solvalou

I can't quite believe the amount of code - some 409 lines assembling to 610 bytes - devoted to two objects that have absolutely no bearing on the game whatsoever! Incidentally, the 68K code is almost exactly half that, some 200 lines.

For those not intimately familiar with Xevious, there are two Sheonites that appear to mirror each other perfectly. They decend from the top of the screen, 'dock' with the Solvalou for a handful of seconds, and the join together before shooting off the top of the screen again. They are indestructible, and serve no purpose whatsoever other than be something to look at.

One of the Sheonites appears to be implemented correctly,
the other, not so much...

It would be perfectly reasonable to assume that they are both handled by the one routine. But it's a strange mix; two independent routines that share a lot of (duplicated) code, but are oddly asymmetrical. And one of them is explicitly destroyed; the other left to be cleaned up after leaving the screen. I've yet to debug the transcoded routines completely, but it has left me scratching my head.

When I first implemented one of the Sheonites, it stubbornly refused to appear at all. Eventually I discovered that the object handler wasn't being called, and that was because a few objects were inadvertently skipped when the handlers were scheduled. In the process of looking for the bug, I decided I didn't quite like the way I'd written the round-robin scheduler, and in the process of cleaning it up and optimising it, I found the bug. Two birds with one stone...

I think I will tackle the Andor Genesis (mothership) next after debugging the other Sheonite. Part of that code comprises the last remaining section of the SUB CPU program to transcode. Nothing tricky at all, just lots of hard-coded references to object attributes.

Sunday, 4 December 2022

More RE and more transcoding!

Some progress in small chunks over the weekend. I've now finished all the (23) ground-based objects, and moving on to the flying objects that are explicitly defined in the map data. These objects don't vary with the difficulty/AI level. So far I have done a lone Kapi, Terrazi, Torkan and currently working on the Garu Zakato (big exploding bomb in Area 9).

Garu Zakato WIP

I've been forced to do more of the RE in order to complete the implementation of the last few objects. So much of the code has been understood now, the last few routines are falling into place fairly easily. For instance the Garu Zakato loops through the bullet objects looking for 16 idle bullets, and initialises them in turn with every 2nd angle in the firing angle table; something I had no idea about until just a few days ago. It also creates a further 4 (flying) objects in the table - objects I had been unable to identify until now.

During my testing I've finally seen evidence of the Neo Geo's 96 sprites-per-scanline limit being exceeded. It's a bit of a false alarm atm, because I'm not optimising sprite usage, and the skipping of areas that produces the "bullet-hell" effect isn't realistic. The good news is that it will be very easy to identify if/when it happens, as the last sprites in the priority - the first ones that will not be rendered - are what I call the "visible display masking sprites" at top and bottom of screen, and mask the 'bouncing' effect of the scrolling background layer.

Saturday, 3 December 2022

Transcoding is flying along!

Progress has been relatively quick through the ground-based objects; it has mostly been a case of filling out a few more simple instruction sequences for handlers that were half-finished. Most of the lower-level routines used in the handlers have already been transcoded and tested.

The first flying enemy has appeared; the lone Kapi in Area 7. Unlike almost every other flying enemy in the game, its appearance is not contingent on the difficulty level nor the AI and as such, it appears explicitly in the map data. Although not technically a ground-based object, it is handled in the same manner.

A lone Kapi appears in Area 7

A total of 21 objects now fully transcoded and tested. That includes no less than 9 variants of Grobda (tanks) exhibiting 7 distinct behaviours; I think there's just another 2 variants to code. And just the one (flying) object left until Area 7 (ground-based or more correctly explicitly from map data) is complete.


Friday, 2 December 2022

Progress is on target!

I've fixed the shooting routines; now there's no doubt they're all targeting the Solvalou!

Those Derotas are on target!

The "bullet-hell" I was seeing had me concerned; it's not as if the firing frequency logic was all in one routine - every object has its own logic, even if it is essentially the same sequence of instructions. But how can that be wrong for every object?

Fortunately it wasn't a mystery for very long at all. The firing frequency for each of the ground-based objects is encoded within the map data, so that as you progress through the map, certain objects fire more frequently to make it harder.

Now I have a build option that lets me specify the starting level, so I can quickly test specific areas of the game. But when I start at a later level, the firing frequency parameters of some/all of the objects haven't been set (as those areas of the map have been skipped) and all hell breaks loose! If I start at level 1, the problem disappears.

I'll now continue with the next ground-based object in Area 5.

Fun fact: encoded within the map data is a parameter that determines at which row on the screen the ground-based objects will stop shooting. This is obviously another variable to temper the difficulty of the game as you progress through the areas.

The Garu Derota (large octogon) is the only grond-based object I've completed thus far that ignores this variable and does NOT stop shooting at the Solvalou until it scrolls off the screen!

A shot in the dark at how this all works!

Today I decided to implement bullets which meant going back to RE some of the code that calculates the bullet trajectory. TBH it did (and still does) my head in a bit; I understand what it's trying to do and roughly how it's doing it, but some of the finer details still escape me. That's not absolutely critical as long as I can replicate the effect of each instruction (or routine). I freely admit that I had to resort to this approach in a couple of low-level routines in Knight Lore, for example.

The lowest level routine takes the absolute values of a (dX,dY) pair between source and target and calculates the ratio of the smaller to the larger; IOW the tan of the angle in the 1st octant. That gets scaled from 0.0-1.0 (0-256) down to 0-32 which then indexes into a table that in turn returns another value (0-32) from a non-linear but monotonically non-decreasing distribution. Looking at a graph of this distribution (below) it doesn't make a lot of sense to me atm*. Then it adjusts this value for 2nd octant if applicable by examining whether dX or dY was larger, and finally adjusts for the correct quadrant by examing the signs of dX & dY in turn.

The function that is applied to the tan of an angle in an octant

* Does this function correct for pixel aspect ratio, so that bullets splay out at regular angles on the screen (and/or at the (purposeful) expense of accuracy)?

That final value is scaled (again) from 0-31, and used as an index into a table of (dX,dY) pairs for bullet trajectories. IOW, objects can fire at 32 different angles.

It has all been transcoded but not debugged; the trajectories are definitely not correct and the ground-based objects seem to almost always shoot in a SW direction regardless of where the Solvalou is located on the screen.

That Garu Derota is firing way too quickly -
Xevious is not supposed to be a bullet-hell!!!

Some objects also seem to be firing a lot more frequently than they should, but that's another issue.

On the note of firing frequency and AI: none of the gound-based objects' behaviour (for the objects I've completed at least) is contingent on the difficulty level set by the dipswitches, nor the adaptive AI logic that depends on the player's skill. The dipswitches and AI level only seem to affect the flying enemies.

When the RE is complete, I'll do a comprehensive analysis of the difficulty settings and the AI logic.

Until then, I need to debug the bullets and the firing logic of a few objects it seems...

Wednesday, 30 November 2022

Progress is on-path!

Domogram was one of the more complicated ground-based objects and I must say the implementation went pretty smoothly. It's the object that seemingly 'follows' paths.

The circling Domograms on Level 7

Of course it's all pre-canned routes programmed via a list of vectors comprising dX,dY and length components. The map data encodes a single byte for the dX,dY pair, which is an index into a table of actual values for the pair. Each Domogram can have up to 64 bytes (32 vectors) programmed into a circular buffer that can hold path data for up to 12 Domograms on-screen.

Up next, Derota. That will incidentally finish off the ground-based objects for Area 2.

UPDATE: Derota finished!

UPDATE #2: Garu Barra, Garu Derota and another Grobda variant fnished, rounding out Areas 1-4 for ground-based objects.

Garu Derota, before being destroyed

The Garu (big) objects comprise a 2x2-tile sprite overlaid with a 1x1-tile sprite in the centre. When the centre sprite is hit, it is removed to reveal the 'destroyed' 2x2-tile sprite. Obviously this requires the centre sprite to have display priority over the larger... fortunately the sprite priorities of the arcade board and the Neo Geo are compatible - it would have been a minor inconvenience to (programmatically) reverse the orders in the code, but not insurmountable.

The next Grobda to appear in the game (Area 3) was one of the 'duplicated' variants. Implementing the Garu objects had given me a hint as to why this could be (indicated by a common graphics glitch before transcoding was complete), and I quickly confirmed it. Although the behaviour of the two Grobda is exactly the same - in this case moving forward when targeted in the crosshairs - one of the Grobda leaves a crater when destroyed, the other simply disappears. The latter, as it turns out, appears on water (where obviously it sinks when destroyed).

Another example of something that can easily be missed in a static analysis.

One gound-based object (technically, 16 objects) that I haven't implemented yet in Area 4 is the Andor Genesis (mothership). I figured I'd leave that until the last of the ground-based objects.

Although the ground-based objects do (visibly) 'fire' back at the Solvalou (and the animation is complete), the bullet objects have not been implemented. It's difficult to tell, but some of them appear to be firing a crap-tonne of bullets, and I'm not sure there's no bugs in that part of the code. So I'm thinking it might be time to implement the bullets - even if they remain harmless atm - just to get an idea of whether the firing logic is correct.

Oh and I fixed the background layer display of tiles from the 2nd bank.

The Nazca Lines are now rendered correctly (again)

Monday, 28 November 2022

I like big Booms and I can not lie!

Well, that was a little easier than I first thought! Maybe because I implemented it incrementally, 2x2-tile sprite support is now (mostly) working. Explosions and Garu Derotas at least are good. I suspect I need to handle flipping differently for 2x2-tile sprites, because Sol Towers don't look right. It'll be a bit ineffcient but fairly straightforward. Right now I'm not concerned with code optimisation.

BOOM - 2x2 tile sprite explosion!

I had a choice of whether to program the 2nd chained sprite in the shadow registers, or on-the-fly as the sprite hardware is being updated. Programmatically it wouldn't have made much difference, as the 2nd sprite only needs tile codes, colours and the chain bit to be programmed.

I was undecided until I remembered that the latter is updated in the VBLANK interrupt service routine, and as such efficiency is critical. Much more efficient if the shadow registers are updated in the main program loop (which should be spinning idle by the time the next VBLANK interrupt fires) and simply block-written into VRAM.

Xevious is now using 266 of the 384 usable sprites on the Neo Geo. They are not all active at the same time, but there will be times when it could run very close to the 96 sprite-per-scanline limit. As I mentioned, there are 75 sprites always active on any 1 scanline at a time, not counting the Xevious sprites. Cutting it fine, but I think I'll get away with it.

UPDATE: I have fixed the 2x2-tile sprite code and all the sprites I have seen thus far in the first few levels are correct. It wasn't a flipping problem, but rather the details of how the sprite codes are mapped amongst the 4 individual sprites. I also fixed a (seemingly) benign bug in the sprite rendering code.

The Sol Tower is finally rendered correctly!

Another issue I had to fix at the same time; because of the difference in coordinate systems, the 2x2-tile sprites need to have their X coordinate adjusted with respect to 1x1-tile sprites. Initially the adjustment was done simply when updating the shadow registers, but then tonight I noticed I could no longer destroy the Sol Tower once it had risen!?!

I quickly realised that it was the sprite X coordinate adjustment that was breaking the bombing-detection routines that use the shadow registers (as I mentioned in a previous post). I looked at moving the adjustment to the sprite hardware register updates, but there's not enough information to (efficiently) determine when the adjustment needed to be made. It's also not ideal to spend more time than absolutely necessary in the VBLANK ISR. It was then I had the idea to have the code write two versions of the shadow registers (for the X coordinates only); the original coordinates for bomb-detection, and the adjusted coordinates for hardware register updates.

In hindsight I'm a little surprised the implementation for 2x2-tile sprites was completed in a day, and as it turned out I had procrastinated for no good reason. It certainly looks a lot better, and I am even closer now to being able to declare that - sound aside - the OSD code is complete.

There are still a few glitches that I have ignored until now; occasionally the score (foreground tilemap layer) is just a black bar, and the map isn't displaying the tiles from the 2nd bank (not visible until later aeras, and I'm sure it was at one point previously). I'll proably continue to ignore those until I get to optimising the OSD routines.

For now though, I'll continue with any new ground-based objects that appear in Area 2. First up is the Domogram - the round vehicles that follow paths and fire at the Solvalou. The RE for them is complete, so I know what I'm up for. FYI they blindly follow pre-canned paths, cleverly designed to give the illusion of some sort of ability to actively avoid Solalou bombs. They won't be the first moving objects that have been (at least partially) transcoded (Grobda claim that prize) but will certainly add to the dynamics of the screen once they're done.

Sunday, 27 November 2022

The making of a Boza Logram.

Finished off the handler for the Boza Logram (Dome Array) this evening. It was surprisingly complicated; some 277 lines of Z80 assembler, transcoded to 146 lines of 68K assembler.

The Boza Logram is represented by just 2 bytes in the map data; the type ($2D) and the Y coordinate of the sprite (left/right position on the display). From there, the initial handler creates 4 more objects - another 3 for the outer Lograms and one for the centre. In the process it patches the handler table for all 5 objects with new handler functions, and sets up the sprite X & Y coordinates, states and attributes.

Without going into too much detail, the subsequent handlers are responsible for setting up sprite codes, animating the sprites, firing at the Solvalou, checking they've been hit, and exploding - not unlike other handlers. The centre handler also destroys the outer Lograms when it is hit, and conversely the outer Lograms update the points value of the centre (600 pts down from 2,000) when they are hit. A lot going on.

The Boza Logram is ~277 lines of Z80 ASM

When I was actively working on the RE, I thought the initial handler installed the subsequent handlers in a 'callback' table for each object. At the time I didn't think much of it, and they were eventually all-but-forgotten as I proceeded with the RE.

Of course, when it came time to transcode this handler I wrote the new code to copy the handler addresses into a new 'callback' element of the object table, and immediately realised I didn't know how these callbacks were invoked!?! It wasn't long before something twigged, and I realised the 'callback table' was in fact the main function handler table - these weren't callbacks at all.

I have since updated the RE and the 68K source, but it goes to show that you don't always get things 100% right when doing a static analysis. I'd claim that when, and only when, the transcode is complete, the RE will be 100% accurate and true.

As far as ground objects are concerned, Area 1 is now complete (although they don't actually spawn bullets when firing back). That's 8 distinct object handlers done and dusted. Only ~55 to go (though 16 of those alone are for the Andor Genesis aka mothership)...

Not 100% decided what I'll tackle next; either finish off the 2x2 tile sprite support, handle ground-based objects shooting back, start on some flying objects, or move onto the next ground-based object handler.

FTR the main CPU code (68K) is now around 3,000 lines, the Z80 equivalent is 11,000 lines. If I assume the 68K will end up at approximately half the number of lines, it means I'm about 54% of the way though the transcode of the main CPU code. My gut tells me that sounds about right. I'd also guess that the SUB CPU code is around 95% complete.

UPDATE: just added a build option to display the area (technically, in base 17) on the bottom right corner of the display.

Fun fact: Hitting the centre of the Boza Logram (first) is worth 2,000 pts. If you hit both it and one of the outer Lograms at the same time (with the same bomb), you'll get an additional 300 pts for the outer Logram. Hitting an outer Logram first scores 300 pts but will reduce the centre value to 600 pts.

Saturday, 26 November 2022

Sprites: bigger and better!

I'm working through the object handlers in the order that they appear in the game. So far I have the following complete; Easter Egg, Barra, Zolbak, Logram, Sol Tower, Grobda (stationary) and the Bonus Flag. You can bomb them, score from them, and (where relevant) they disappear in a crater. The last ground-based object in Area 1 is the Boza Logram, which I haven't done yet.

Fun fact: the Easter Egg is worth 10 points, only because bombing anything updates the score from the object table 'POINTS' element and the (default) value of 0 (after the table was zeroed) equates to 10 points in the game.

Although the Logram "fires" bullets at the appropriates times, the code to spawn a new bullet hasn't been written. Technically that simply creates a new 'bullet' object and isn't strictly part of the Logram object handler once it leaves the opening, so I can say the Logram handler is complete.

It's also worth noting that there are a few object handlers in the code that aren't actually used. These are mainly identical copies or variants of objects that are used. One example is a Logram variant whose firing frequency isn't actually defined, and if used, I believe would use the firing frequency of the last object that occupied that slot in the table.

At this point I've started to work on support for 2x2 tile sprites. All the explosions, and the Sol Tower, comprise 2x2 tile sprites, as well as a handful of other ground-based objects in the game. To this end, I have 2x1 tile sprites (double height) coded and tested. In fact one half of the explosions are right now. That was the easy part; simply setting the (NeoGeo) sprite height to 2 tiles and adding the approriate sprite code & colour. Note that AFAIK, only 1x1 and 2x2 tile sprites are actually used by Xevious.

Half of the explosion (2x1 sprites) is right now

The way I plan to add support for 2x2 tile sprites is to double the number of NeoGeo sprites allocated to the Xevious sprites, and simply chain pairs together to get the double-width element of the equation. There will need to be some tweaking of the coordinate because of the difference in coordinate systems between the Xevious arcade and the NeoGeo, but I'm hoping it won't be a lot of work.

What I do have to be wary of though is the 96-sprites-per-scanline limitation on the NeoGeo. The foreground tilemap layer is currently 36 sprites, the background is currently 37 (active) sprites, and adding the two visible display masking sprites makes 75 active sprites on EVERY scanline so far. Given that there are up to 64 sprites on Xevious, I'm going to have to ensure that NeoGeo sprites are only activated when absolutely required.

That's going to be a little bit of work and probably a few days before I get it working properly. Then I think the OSD layer on the NeoGeo will well and truly be finished.

Friday, 25 November 2022

Shooting for the stars (in the absence of anything else to shoot at)

Quick lunch update; shooting is now working (there's nothing to shoot at though).

Up to 3 Solvalou bullets can be active on the screen

I've also indentified an area of shared RAM which appears to be used to signal the SUB2 processor to start/stop certain sounds during the game. This ticks off a handful of variables in the RE whose purpose was unknown until today.

Next up, having bombs actually blow things up! The scoring should already be in place, although things like updating the high score and awarding extra Solvalou have not been transcoded yet.

UPDATE: Slow afternoon (the best type of Friday afernoon) and so I managed to do a bit more Xevious. Bombing is now complete; you can destroy any objects whose handlers have been transcoded up to the point where they check they've been hit.

It's always fun blowing things up!

There does seem to be a glitch in the routine that displays the score... almost as if interrupts aren't disabled when VRAM is being accessed; sometimes the score is a black line?!? Hmm....

Targeting the... targeting code!

More progress today, and some further complications arise.

I've coded the 'targeting' function where the crosshairs flash red when hovering over a ground-based object, including the hidden sol towers.

Cross-hairs flashing red over a Sol Tower

Herein lies the complication; the Z80 code uses the shadow sprite RAM for coordinate comparison between the crosshairs and the ground-based objects. Similarly, the shadow sprite RAM is used in other 'collision-detection' routines, like collecting the bonus flag. The issue is, the shadow sprite registers are in OSD - in this case Neo Geo - format, and idealy only accessed in the OSD layer.

For now, I've ignored that fact and just written the code in the main CPU file. But of course ultimately it can't reside in this file because it's strictly a platform-independent core.

Moving it into the OSD layer is troublesome from a few angles, not least of which is efficiency (especially if I write accessor functions for sprite coordinates). And I can't move the whole routine as I also don't want any core game logic in the OSD layer.

On the 68K core though, the sprite coordinates stored in the object table are simply 16-bit values, scaled with respect to the Neo Geo register copies in shadow RAM (themselves shifted into high bits). It would therefore be no less efficient to compare sprite coordinates from the object table as opposed to the shadow registers. I'll sit on it for now, but I will probably go this way; deviating from the original code, but arguably only semantically.

Other progress comprises being able to collect the Bonus Flag, and score either 10,000 pts or an extra Solvalou, depending on the dipswitch setting. I have a build option for revealing the location of the Sol Towers. And finally, I've coded the shooting routines, but haven't debugged them yet. Shots are fired, but they don't behave as expected.

10,000 pts for collecting the Bonus Flag

Next is obviously debugging the shooting, and then I'll code the routines for bombing ground-based objects. They should be very similar to the targeting routines I did tonight. Then there will be a lengthy process of finishing all the handler routines for the ground-based objects.

One last aspect of the OSD layer I need to add is the 2x2 sprite hardware support. I have an idea of how that's going to work, but I will probably leave it for a while yet.

Thursday, 24 November 2022

I hate computers. Mine is bombed...

%$#@^%!@$#%^!$@&^#%$&!@^%#$&!%@^$# PCs... another machine crash.

Despite that, have implemented bombing. Nothing gets hit or explodes, but you can drop bombs.

Encountered an interesting complication; Xevious sprites can have more than 1 color entry as 'transparent'. The crosshairs that are also used for the bomb target use different CLUTs at different times, and some of them have transparency set for pixel values 0, 2&3 simultaneously. That means I will have to modify the source to use different tiles for the sprite at different times...

Don't know how many days I'll waste trying to restore my PC. I don't even know what the failure was atm. I thought it was the RAM, which I changed, and it seemed to fix it, but then it stopped booting again when trying to install NVIDIA drivers... grrr!!!!! 

Tuesday, 22 November 2022

Cranking the handle for a while to come

Xevious is progressing well, although there's not a lot of exciting new development to blog about. I've been filling out object handlers with just enough code to display the object (if possible) and allow the map parsing and the game to continue. Still a handful of glitches but, as of now, it doesn't hit any unhandled objects or functionality until Area 7. Some of the Grobda (tanks) are even moving now!

Random screen shot - revealing Bonus Flag location

I've coded the pseudo random generator; it still requires more thorough testing but should generate the same sequence as the original. As a result, it no longer hangs at the bonus flag on real hardware. I also have a build option that reveals the bonus flag locations; it simply sets the sprite tile to the 'flag' rather than the invisible tile. I'll do something similar for the Sol Tower when I get to it.

The object handlers aren't finished, so some glitches
like one of the Derota colours above

Consequently, the SUB CPU code is almost complete now. Once the map can be completely parsed for all 16 areas, I'll look at finalising the code for it.

The plan moving forward is to complete a skeleton implementation of all the object handlers. Then it'll be a matter of going back and fleshing out the functionality for each object. A handful of the simpler ones (eg. Barra, stationary Grobda) are already complete. In order to test the full lifecycle of the objects though, I'll have to implement firing and bombing first, and I may as well implement scoring while I'm at it.

There should be steady, if not exactly spectacular, progress over the next few weeks. And of course holidays and family time will interrupt that at some point.

UPDATE: The game runs through all 16 areas on the map now, which means all ground-based object handlers at least have stubs. It also means the SUB CPU implementation is all-but-complete!

TRIVIA: Area 14 has NO ground-based objects!

Saturday, 19 November 2022

Running on real Neo Geo hardware!

No work on Xevious today but with a handful of free minutes I thought I'd see what happens when I try to run it on real hardware. After fumbling with forgetting my Terraonion login, and then forgetting how to load up homebrew on the NeoSD, I eventually got it converted and loaded into my AES console.

My AES is set up in my (tiny) games room which, sadly, doesn't get a lot of use atm. If I do get the time to be in there (without my 7yo following me in), I'm usually running games off my MiSTer. But the BlisSTer hub is actually broken (again), so it has been a while. I'm running our old LCD TV and a 1084S clone which, for some reason, is only showing black and white for the Neo Geo atm...

Xevious on a real life AES

It basically works but there are a number of issues, most of which don't really concern me too much at this point in the development:

  1. Plenty of flickering on the screen; the scroll routine is doing a lot more work than it really needs to, and I have no doubt I can make it at least an order of magnitude faster than it is now.
  2. The screen is scrolling slowly; I'd guess every 2nd VBLANK is being missed. Again (see above), plenty of optimisations to be done.
  3. The credits are incrementing automatically, even when the UniBIOS is set to MVS mode. It was only ever a quick hack, as the custom Namco chips actually handle coin inputs.
  4. (Seemingly) when the credits reach a certain point, the foreground tilemap layer shifts a couple of rows down the screen. It does this whether on the title screen or in-game.
  5. When the bonus flag is being added to the map, it freezes (permanently). This is likely because I did a very quick hack for the pseudo RNG and simply read the seconds from the RTC. Because the code will re-generate the position if it is too close to the Solvalou, under MAME it often freezes for a second or two here. I'm confident fixing the RNG will alleviate this issue.
My biggest concern is that it behaves differently than under MAME, which means MAME isn't quite as accurate as they would have you believe. I recall having similar issues with Donkey Kong; the first few attempts (NGCD at the time) actually didn't produce usable video! I seem to recall resorting to another emulator to debug the issue...

Regardless, it's certainly encouraging that it is actually running and producing something that resembles Xevious. I have a feeling that MAME is a little more forgiving with the VRAM accesses though, and I will have to make sure I fully optimise that aspect for the real deal.

Thursday, 17 November 2022

This project is starting to tank (and that's good!)

Some free time today to work on Xevious. Added a few more ground-based object handlers - enough of each of them to see them appear at least - and stubs for other functions that needed to be defined. A few of the objects have pulsating colours! I also took the opportunity to fix the sprite placement - they're exact now.

A few more ground-based objects implemented

One thing I forgot to mention last post is that I've just split the Xevious core source into 2 files; main and sub for each of the CPUs. The main in particular is going to be huge, and the only shared information between them are a couple of routines and a handful of memory variables, so it made sense. The sub will likely be finished sooner rather than later as well.

I'm finding I only need about half the number of 68K instructions to manipulate the object tables in the handler functions as compared to the Z80 code - often even less! As an example, the handler for the Barra (Pyramid) is 23 instructions in Z80, and only 10 in 68K! And using .equ for member offsets in the table, a lot of it is self-documenting.

I only just realised that I had the sprite layer on top of the foreground tilemap layer! Only noticed when the Grobda (tanks) scrolled onto the screen in front of the High Score text! Fortunately all that was required was to change two .equ values and re-assemble - it pays to take the time to avoid magic numbers in your code!

My choice now is to continue with map parsing and adding handlers - or at least stubs - for new objects, or implementing bombing. I'm undecided. In any case, I might have a break until tomorrow...

A wild Barra appears - another significant milestone!

Another significant milestone tonight - a ground-based object appearing on the map and scrolling with the screen. It means that a lot of the infrastructure code is in place now, and the bulk of the remaining transcode is filling out handlers for the various objects. That's not to say that I don't have an absolute crap-tonne of work ahead of me, just that it should be relatively straightforward.

The first ground-based object to appear, a Barra

The exact location of the sprites look like they require a bit of minor tweaking, but I was waiting until I could gauge them against the map. And any tweak will be universal for all sprites, so it should be trivial.

In the process of debugging this latest functionality, I verified that the main routines for the MAIN and SUB CPUs still have time to spin between VBLANK interrupts. This is particularly encouraging given that I still have some pretty brain-dead code in the video routines. It was left that way for 2 reasons; logically simpler code is always easier to debug, and of course I wanted to push ahead and implement more functionality.

As the transcode evolves I'm finding myself going back and refining some of my choices; primarily byte vs word variables, and register usage.

A lot of the time it's more efficient to use a 68K word in place of a Z80 byte, particularly if that byte is used as an offset from a base address/register, or in a 16-bit calculation of course. And in this instance memory is hardly scarce either.

Register usage is always something requiring consideration. My rule-of-thumb is that D0-D1 are used before any other data registers in a routine, D6 as an object table offset, and D7 for loop counters. I've reserved a few address registers for consistent usages as well; A5 for the object table, and A6 for the jump table routines. I haven't been in any danger of running out of registers just yet...

Next is a few more ground-based objects (low-hanging fruit) before I tweak the sprites and then I might look at bombing, so I can blow a few of them up and have a few object handlers transcoded for the full object lifecycle. That will enable me to code the scoring routines.

Wednesday, 16 November 2022

Another dormant bug that only awakens on the Neo Geo

Generally during RE, and sometimes during transcoding, a few bugs rear their ugly head. Some, like in Knight Lore, are benign on their native platform (hence the reason they're not found) and sit dormant, until someone comes along and transcodes the game to another platform.

I've found a few relatively harmless ones thus far in Xevious, mostly initialisation bugs that ultimately don't seem to affect the game. But tonight I discovered one that was crashing the Neo Geo until I found it and coded a work-around. Interestingly, it has some similarities with the Knight Lore bug.

In Xevious, as the map is built line-by-line as it scrolls onto the screen the SUB CPU maintains a pointer to the object table for that area. Each invocation it compares the scroll counter with the value pointed to in the object table, waiting for a match. When they match, it means it's time to add the next object to the map.

The trouble is, the SUB CPU routine is running whether or not the game is playing, and in fact, it's running before the MAIN CPU even initialises either the scroll counter or the area object table pointer with actual data. Due to the RAM test/initialisation functions, both the scroll counter and the area object table pointer are set to 0.

On the arcade hardware, the code looks at the value stored at the pointer ($0000) which happens to be the first location in ROM, in this case $3E. Since the scroll counter is $00, it never matches, and in fact will never match until after both it and the pointer have been initialised properly. So the bug lies dormant.

On the Neo Geo however, location $00 is part of a vector and is, in this case, $00. That matches the scroll counter before it is initialised, and so the code attempts to read the object type from the area object table. Again this is reading from low memory and returns an invalid object type, outside the range of the function table which subsequently causes it to jump to an arbitrary address and - crash!

The bug in Knight Lore also had a null pointer, and the code was trying to write to ROM at $0000 which of course is also benign.

Anyway, interesting to see how many bugs still lurk in these old arcade games.

The fix wasn't particularly elegant; just return if the area object table address is null.

FTR I'm still adding the infrastructure for handling objects added from the SUB CPU as the map is constructed. There was a little more code than I had remembered. It involves nested jump tables so a little bit involved, but nothing I haven't done elsewhere in the code. Interestingly, where I left off it should have crashed - but didn't!?!


Tuesday, 15 November 2022

Joystick inputs working!

Again, not a lot of work done on Xevious today, but I've got joystick inputs working and you can move the Solvalou around the screen. It also moves somewhat randomly in attract mode.

The Namco custom chip(s) are responsible for reading joystick inputs and mapping them to one of 8 valid directions (0-7). It returns 8 for no or invalid input. The most efficient solution was a lookup table to map Neo Geo joystick inputs to 0-8.

Slower going atm as I invariably make silly little mistakes on the transcode, but they should become less and less frequent as it progresses. Things unique to the 68K code like not sign-extending the dX/dY values to a word when doing word arithmetic on sprite coordinates.

There are also instances where I've converted byte variables to word to make the 68K code more efficient (avoiding having to clear the top half of the word, for example). The downside is remembering which I've changed and which I haven't... unless I resort to - ugghhh - Hungarian notation. Nope.

Now that inputs are hooked up, I'm going to move onto the object table that is read as the map is constructed, and places the sprites for ground-based objects.


Sunday, 13 November 2022

A spritely Solvalou appears!

Very quick update, busy weekend (Real Life)...

Not a lot of time to do much, but the Solvalou and crosshairs are now rendered. I've transcoded a little more of the attract mode gameplay so that it actually enters and stays in there now. I was hoping to get time to hook up the player inputs but didn't happen.

Attract mode gameplay

As I mentioned last post, once I get the Solvalou moving around, I'll start on the map data processing and getting ground-based sprites appearing. There's a fair amount of code in the SUB CPU required to make that happen, but a lot of it is object-specific, which means I should start to see some objects fairly early-on in the process.


Saturday, 12 November 2022

Who'd have thought it would be so hard to get the Neo Geo to emulate sprites!?!

Wow, that would have to be the most frustrating part of the transcode so far! What should have been relatively straightforward - implementation of the Xevious sprites on the Neo Geo (a bloody great sprite engine itself) - resulted in no less than 3 days of banging my head against a brick wall!

There were plenty of red herrings along the way, including 'corrupt' sprites that were actually the result of the VBLANK interrupt routine taking way too long to execute and updating VRAM whilst the display is active. I thought it was the sprite routines writing to the wrong addresses in VRAM and corrupting the background layer. Numerous other little bugs but one after the other, I fixed them and the problem remained. I was getting ready to give up...

And then I discovered a cut 'n' paste error which meant I was writing to the wrong area of VRAM when updating the sprite X coordinates. Not corrupting the background layer though, but rather corrupting the Y coordinates of the sprites themselves. I must have checked that line of code a dozen times, and never saw it!!! 😫

Anyway, much relief now to have finally found the problem.

The sparkle on the logo is a Xevious sprite

Now I can look at tidying up the sprite code; removing the debug code and making it more efficient.

It's not entirely finished as I need to add support for double height and/or width sprites, which will be fairly trivial on the Neo Geo. But for now, as they're seldom used, I'll press ahead and start transcoding the SUB CPU routines that parse the object table for the map and create/place the sprites for the ground-based objects. Actually, first I'll add the Solvalou, and get that moving around the screen!

It's another big milestone as this means the OSD layer (and its interface) is all-but-complete, allowing me to transcode the vast majority of the game now without having any more technical challenges on the Neo Geo. Almost to the point of cranking the handle now... almost!

Thursday, 10 November 2022

To what end?

I've implemented, but not tested, the routines that update the shadow sprite registers and copy them to sprite hardware.

In the original code, the shadow registers (as the name implies) are RAM-based copies of the hardware registers and have the same format. There is a SUB CPU routine that periodically updates the shadow registers from the object table, with the necessary bit-twiddling to get data into the right format. And another routine elsewhere that simpy block-copies the shadow registers to the hardware.

Somewhat stupidly, my initial implementation was to preserve the original format of the shadow registers, and then convert on-the-fly to the Neo Geo in place of the block copy. In fact I had gone as far as coding the latter routine, but then realised how pointless this was when coding the routine that filled the shadow registers from the object table. A double bit-twiddling nightmare for no good reason.

Instead, I moved the shadow registers into the OSD (Neo Geo) layer together with the routine that fills them from the object table. And then simply replaced my previous bit-twiddling routine to update the Neo Geo sprite registers with a number of what are esentially block byte copies. Too easy.

In order to test any of this though, I need to transcode some routines that use the object table. As I mentioned in an earlier post, the first instance of that is the sparkle chaser around the Xevious logo.

I spent some time thinking about the most efficient way on the 68K to organise and access the object tables. On the original arcade game, they comprise a number of 2-byte-per-object tables on 256-byte boundaries; so they can be accessed for a single object by simply updating the H register (L=0/1), for example. I've opted for a single 32-byte-per-object table, accessed using either Address Register Indirect with Displacement or Address Register Indirect with Index addressing mode, depending on specific circumstances.

It really does result in some nice, compact code - compared with the Z80 - but there is one stick-in-the-mud... endianity.

The X and Y sprite coordinate values are the only 16-bit tables; the rest are pairs of 8-bit values. It is very advantageous to store the X & Y with the natural endianity of the host, that's a no-brainer. But given the Z80 and 68K have opposing endianity, this causes an issue when pairs of single-byte value are accessed via 16-bit register pairs on the Z80.

An example; in the so-called 'state' table, byte0 is the state, and byte1 is the attribute. Ordinarily they are manipulated using byte accesses, but there are a few instances where they are initialised using the HL register pair. eg:

ld  hl, #0xF30
ld  (obj_code_tbl+0x1E), hl

This initialises the code for object 0x1E (byte0) to 0x30, and the attribute (byte1) to 0x0F.

When I naively transcode that, I'd end up with something like:

move.w  #0xF30,(0,a5)

...which of course puts 0x0F in byte0, and 0x30 in byte1 - ie. swapped around!

So this gives me two options; either swap the order of bytes in the immediate operand of the move instruction, or swap the byte order in the table. I was leaning toward the latter, mainly because it's easier to forget to swap the bytes manually, but ultimately decided on the former - since it's not done in a lot of places, and I like the idea of preserving the original table order.

I should note that I'm using .equ values for all the displacement values, which not only negates the need for me to remember them for hundreds of instructions in the transcode, but they're also self-commenting!

move.w  #0x300F,(_CODE,a5)

Now back to sprites, which should be working, but they're not. Again, par for the course...