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...

Monday, 7 November 2022

Finally ready to tackle the Xevious sprites!

A bit more transcoding, scheduling the SUB CPU routines, and hooking up the Neo Geo dipswitches.

In my not-quite-complete RE, I never did find the code that was displaying the 1UP and 2UP text above the player scores. Today I found it - by accident - in the SUB CPU code. I didn't expect it to be in there, but it handles the display and flashing of the respective strings. Incidentally, it was the last and only unidentified main routine in the SUB CPU.


High Score Table, 1UP,2UP flashing

I had tacked a few of the SUB CPU routines onto the end of the MAIN BLANK IRQ handler as a quick hack to implement scrolling, but that started to cause issues when the SUB CPU code wanted to access video memory which disables interrupts. So I took a step backwards and looked at the timing and synchronisation of execution between the two CPUs. Both CPUs execute their main loop once, and then spin waiting for the next VBLANK.

It was a simple matter having the SUB CPU main routine called from the MAIN CPU main routine - I think that should do the trick. It's working for now at least. I also implemented the SUB CPU main routine round-robin function scheduler properly; not that it's doing much atm other than scrolling the display and displaying 1UP/2UP as above. But fleshing that out now should be trivial.

I've defined the Xevious dipswitches for the Neo Geo soft dips settings, and the lives setting has been coded and tested in the core. In order to avoid excessive bit-twiddlng each time the dips are read - once every VBLANK - I'm preserving the switch (bit) ordering in the Neo Geo even if the options aren't "nicely" ordered. Who cares, right?

And with that, it's time to start coding for the Xevious sprites. There's not a lot else that I can transcode that is meaningful without being able to at least play a game. The objects (sprites) in the game comprise the main data structures and the bulk of the code, so I'll need to optimise that for the 68K. It would be silly to attempt to preserve the exact structures used (and optimised for) the Z80. But it should end up more efficient than the Z80 code when all is done.

And sprites being sprites, technically it should be a lot easier on the Neo Geo than trying to emulate the tilemap layers. Just a lot of bit-twiddling to convert Xevious sprite register data to Neo Geo sprite register data.

Milestone 1 complete!

Some free time this morning and I've managed to code the scrolling routine. It's brain-dead and brute-force, but the scrolling is derived directly from the hardware scroll register calculated in the original code. 

Title screen and scrolling map

There's plenty of optimisations that can be done for the video access, but that can be done later. More importantly for now, the tilemap functionality in the OSD (Neo Geo) layer is fully functional, which means I can focus on the transcode itself and not have any technical (Neo Geo) hardware issues to deal with - at least until I have enough of the code in place to start working on Xevious sprite support.

Here's a video of the game running thus far:


I'll work on transcoding more of the high-level code before tackling the sprites. The first instance of a sprite is the sparkle chaser on the logo; that'll probably be the first sprite I try to implement. Or maybe the Solvalou. Then I'll implement the sprites for the ground-based objects on the map...

Cooking with gas!!!

Yah! A few more frustrating bugs squashed and the tilemap layers are now all-but-done. They've been tweaked for proper centering, pixel-perfect alignment between layers, and it's all - finally - looking good to implement the scrolling next.

Title screen, which uses both tile layers for the logo

The map screen is back!

Part of the scrolling implementation is adjusting the visible areas at top and bottom of the background layer. That's trivial, because I use a pair of black opaque sprites to mask the not-visible extents.

I should note that the actual code for updating the colour and video RAM areas are far from optimal at the moment, and I plan to fix that at some point. In order to facilitate getting everything right, I reverted to the original video RAM layout and went through the same steps as the original code in calculating RAM addresses from row and column pairs etc. In my Neo Geo layer, I effectively have to reverse that calculation again. It would be far more efficient to bypass the RAM altogether, and I'll look at doing exactly that at some point.

My previous implementation of the scrolling map hasn't gone to waste either; I'll have to use some of that mechanism in the new implementation. I'm actually hoping I can knock out scrolling in one more session at the keyboard.

I think I've just jinxed myself now...

Saturday, 5 November 2022

Too many sprites spoil the transcode!

A few frustrating hours thinking that my background layer sprites weren't visible, when in fact buggy code elsewhere meant they were being created and then wiped before I could see them. I must have walked through the same routines a half dozen times in the last 48 hrs looking for a bug that didn't exist... though that's arguably par for the course for any software development really.

Background and foreground layers - final implementation?

It may look like I've taken a step backwards from the last screen shot, but I think I've finally nailed down the implementation of the two tilemap layers on the Neo Geo. You'd think that 384 sprites - 96 on any scanline - would be more than adequate for a 1982 Z80-based arcade game with discrete graphics hardware... but when you have to scale down the target system sprites and emulate dual tilemaps it can become an issue! I'm confident though that I've managed code around these limitations with a solution that allows me to coordinate the original scrolling variables with what's happening on the Neo Geo screen relatively easily.

Hopefully - it's yet to be tested!

Coined up and started a game... can you tell?

It may not look very good, but the only thing missing from the latest implementation is the background layer colour/attribute RAM, which is why the tiles aren't rotated correctly or have the correct colours. The code has been implemented in other - obsolete - routines already, I just need to port it across. There is one issue though; 1 bit of the colour index is dependent on the tile code, which means in theory the tile colour needs to be (also) updated whenever a new tile code is written to the background layer video RAM.

Next step is to complete the abovementioned background layer colour/attribute RAM implementation and then I can look at scrolling. If that's as straightforward as I'm hoping it will be with my new implementation, then within a few days I should have complete, perfect, implementation of both layers! Then I can start fleshing out the rest of the code in preparation for adding the Xevious sprites!

And there's the small matter of finishing the RE as well... 😉


Friday, 4 November 2022

Leaning to the left...

Not quite, but getting there. Screen is rotated left now, instead of right.

Original vide RAM mapping, rotated left

I think the the top 4 tile rows of the screen aren't visible on the arcade machine, as the HIGH SCORE text appears on the 4th row in video RAM (confirmed on the original).

I should try to understand the MAME tilemap macros, it would probably give some insight into the visible areas and how it decodes the mapping...

void xevious_state::video_start()
{
m_bg_tilemap = &machine().tilemap().create(*m_gfxdecode, tilemap_get_info_delegate(*this, FUNC(xevious_state::get_bg_tile_info)), TILEMAP_SCAN_ROWS, 8, 8, 64, 32);
m_fg_tilemap = &machine().tilemap().create(*m_gfxdecode, tilemap_get_info_delegate(*this, FUNC(xevious_state::get_fg_tile_info)), TILEMAP_SCAN_ROWS, 8, 8, 64, 32);

m_bg_tilemap->set_scrolldx(-20,288+27);
m_bg_tilemap->set_scrolldy(-16,-16);
m_fg_tilemap->set_scrolldx(-32,288+32);
m_fg_tilemap->set_scrolldy(-18,-10);
m_fg_tilemap->set_transparent_pen(0);
m_xevious_bs[0] = 0;
m_xevious_bs[1] = 0;

save_item(NAME(m_xevious_bs));
}


To be continued...


Thursday, 3 November 2022

One step forward, and 2 steps backward!

Now that I have enough of the high level code in place to coin up and start a game, I've gone back to fix up the background scrolling. The original issue is that I wasn't really emulating the scrolling hardware properly, but rather optimised it for the gameplay scrolling, and as a result it came unstuck when the game was either setting the scrolling offset arbitrarily, or writing non-map data to the background.

There are 3 components to the scrolling that need to be emulated; the software scroll_cntr variable, the video ram mapping, and the hardware scroll register. Ultimately the software modifies the software variable directly and the hardware scroll register value is calculated from that. And the video ram mapping is optimised for a rotated monitor. And I have to admit, I am having trouble wrapping my head around the algorithmic relationship between the three. Hence the initial optimised implementation which simplified the video memory mapping and - I have to say - fudged the hardware scroll variable somewhat.

In attempting to rectify this, I've actually taken a big step backwards and decided to re-think the entire architecture of the scrolling background layer. Not only the above-mentioned aspects, but also the Neo Geo-specific implementation (ie how it uses sprites).

First step was to preserve the original video RAM mapping and transcode the routines that handle map rendering more accurately. In the end it wasn't as painful as I thought, because I can forego the screen flipping considerations and 16-bit operations are a lot simpler on the 68K than the Z80. To give you an example, the routine to calculate the video RAM address from row & column is 22 instructions in the original Z80 code (albeit with flipping), and only 8 instructions on the 68K.

Reverting to my original (test) code to render the background layer, I not only see the map being rendered (correctly again), but I can also now see the Xevious logo that is written directly into the background layer video RAM.

We finally get to see the map again

Tiles written to the background layer are now rendered correctly

Note that because the routines for background and foreground layers share the same calculation routine, the foreground layer tiles are now incorrectly positioned. But that's simply a matter of updating the foreground tile layer access routine to use the original layout. That's what I plan to do next...

As I mentioned, I've re-thought the implementation of the background layer, and it will likely require a few nights and a bit of experimentation to get it right. But I am hoping it will simplify the scrolling and allow the physical display to sync up with the software scroll values used in the code.

One other thing that occurred to me too; the direction of the rotation. I'm not sure how or why I chose to rotate the screen to the right in Donkey Kong (and hence all my other projects since), but I thought I should check against the one (and only) "commercial" tate-mode Neo Geo game; Neo XYX.

As it turns out, Neo XYX rotates the monitor to the left. Programmatically there's little difference; you have to either reverse the X or Y coordinate depending on which way you rotate. The Neo Geo video RAM access mechanism lets you selectively increment or decrement the address after each access, so neither is more preferable to the other. But I do think it's a good idea to use the same orientation as Neo XYX, because people aren't going to want to rotate their monitor different ways for different (tate) games. And if/when Neo Geo tate mode is supported in MiSTer, they're likely to favour the orientation of Neo XYX.

So that's another modification I have to make to my code before I can progress.

[I've just checked my Neo Geo tiles; I've only got one rotation in the set (because you can always toggle the flip bits to rotate the other way) and they're actually rotated to the left. Looking at my code, it does in fact toggle the flip bits when rendering each tile. So I guess I'll save 1 instruction per sprite update now!]