Tuesday 7 September 2021

Galaxian Part 7 - Preliminary Impressions

Whilst I've been waiting for Miro to digest the previous posts and hopefully develop his framework to the point I have described, I've actually been doing a little bit of the transcode. I'm not going to go into any specifics here as I'd like to document it in some form of ordered fashion, but I will make a few observations, if for no reason other than hinting that I haven't yet abandoned the project.

If there was ever any shadow of a doubt that Scramble was based on the Galaxian codebase (and there really wasn't), it is beyond question in my mind now.

It of course helps that Scott Tunstall reverse-engineered both games - and hence the disassemblies use common terminologies and labels - but there are plenty of instances where those identically-named variables actually share the same address in both games.

Despite having transcoded only a handful of routines thus far, I've already encountered common code. And it's very clear that both games also share the overall architecture of the code. It's an absolute certainty that Scramble code was derived from the Galaxian codebase.

Now that bodes well for the project. Aside from the obvious advantages that a familarity with the architecture presents, I can also say with confidence that the assembler code lends itself very nicely to a higher level implementaion. And that's certainly not true of all assembly code.

And touching on the subject of process I discussed in Part 5, I'm happy so far with how things are turning out when preserving the original assembly code interleaved with the C code. During the course of this blog I'll be posting examples to illustrate various aspects of the transcode, and you can make your own mind up, but I think it adds more than it detracts from, for example, the 'cleanliness' of the higher level source file.

At this stage of the transcode, particularly given the architecture of Galaxian, there is a lot of tedious typing to be done; defining functions and their prototypes, adding variables to the working RAM structure, defining tables etc. I'll likely press on with this aspect of the transcode whilst waiting for Miro, so I can't at this point say when I'll post the next blog entry.

Thursday 2 September 2021

Galaxian Part 6 - Verifying the framework

Before we start on the transcode proper, it would be prudent to test the display output portion of the framework, as this is what we will be using to verify the transcode itself. The purpose is to verify the conversion of the graphics palette, tile and sprite data, the ability to display tiles and sprites, and that the correct output is displayed from the tile memory and attribute areas. Correct output from the sprite registers would also be nice, but is also slightly more complicated and not critical at this point.

First step is to simply dump the tiles and sprites to the display, using the same mechanisms that the ported game will employ. For my Allegro prototype platform, that's copying a region of my sprite sheets for each of the 256 8x8 tiles or 64 16x16 sprites from a memory bitmap to the display. The output for Galaxian looks like this:

Galaxian Tiles and Sprites

Next step is to render a display directly from the Galaxian tile memory (character RAM or CRAM at 5000h) and tile attribute memory (object RAM or OBJRAM at 5800h). Note that the OBJRAM also contains the sprite registers, so these could also be rendered. This is essentially implementing the function that will update the Galaxian display as the game is running, and requires detailed knowledge of the CRAM and OBJRAM structures and mappings; an exercise I'll leave to the reader.

We of course need some (sensible) data in the CRAM and OJBRAM memory blocks, and the easiest way to do this is to run MAME with the debugger enabled, invoke the debugger at an aesthetically interesting point in the game, and SAVE the entire Z80 address space to a file. Then it's simply a matter of reading the binary dump at startup, copying the RAM and OBJRAM areas to suitable buffers if required, and calling the display routine. You should see something like this:

Eye candy before we've even started!

I think the only sprite visible (active) in this dump was the player's laser atop the ship - it's not visible here for reasons unknown at this point. But quite obviously this is pretty close to the actual output, although there will be further enhancements required to the display routine later in the process.

Now we can be reasonably confident that once we implement any code that writes to CRAM and/or OBJRAM, it will be faithfully rendered on the host platform display. Take a good look now, because it's likely going to be quite a few weeks before we see anything like this particular output again!

And that's it for the preliminary work. Next up is to start the actual transcode. No better place to start than the Z80 reset address, 0000h, which we'll implement in the galaxian () function called when the framework has been initialised and we're ready to execute the game.


Tuesday 31 August 2021

Galaxian Part 5 - Goals, Aims & Notes on the process

Before we dive into the actual transcode, I wanted to document a few things about the goals and aims of the project, along with some notes about the process.

Goals and Aims

First and foremost, the aim of the project is to document and preserve both the behaviour and the implementation of the original code which, not surprisingly, tend to go hand-in-hand.

Preserving the behaviour means that the transcode will look, sound and play identically to the original. This is pretty much implicit in a transcode, as opposed to a port, since you're translating the code from one language to another. It is in fact the next closest you can get to an emulation.

Documenting & preserving the implementation is also somewhat implicit in a transcode, but not necessarily 100% guaranteed. It is possible to take some liberties with the code, taking advantage of features and/or efficiencies in a higher-level language and more powerful host platform. A transcode that literally mimics Z80 register operations, for example, may be inherently accurate but is not particularly useful; it does little more than the original ASM code in terms of documenting the implementation and also obscures the underlying program design with a tediously verbose reimplementation.

It makes much more sense to make full use of aptly named C variables and data structures, C constructs such as if/else, switch statements and while/for loops, and passing parameters to C functions.

It's important though to avoid any code enhancements that modify the underlying algorithms, even if there are underlying inefficiencies or even bugs! If the ASM code happens to inadvertantly check the same condition twice within some code paths - replicate that in the C code. If the ASM code uses a clumsy sequence of instructions to calculate various conditions - replicate that in the C code. If there's a bug that causes stray pixels on the screen in some cases (Knight Lore on the ZX Spectrum) - replicate that in the C code! We want the authentic experience, not a sanitised one!

The real trick is finding the balance; implementing the original intention as faithfully as possible whilst taking advantage of the higher level language, optimising out the (generally few) insignificant details and preserving the important ones. And this is of course all subjective!

Secondly, another major goal of the project is to produce a high level implementation that is as easily ported to as many (generally more capable) systems as possible. Keep the C code as simple and as platform-independent as possible, avoiding potential data size and endianity issues. As a rule I use integer data types as defined in <stdint.h> and avoid bitfields altogether.

As an aside, the caveats I've encountered tend to be overflow and signed/unsigned nuances of the original CPU and the host platform C implementations. Certain 6502 condition codes for example are particularly mind-bending.

The process

It's worth documenting a few notes about the process that I've found to be useful along the way.

Any relatively complete reverse-engineering (RE) will have labels for all the memory locations in use, and names for all the functions. These labels and names we want to use verbatim in the C transcode.

In the past, and admittedly a legacy of the 6502 zero-page register space, I've created a C structure to contain all the memory variables in a program. This is perhaps a little verbose and can be terribly inefficient on some host platforms if you attempt to use alignment and/or packing to facilitate certain nuances of general assembly programming (eg clearing or copying blocks of memory). Still in two minds about this, but thus far it hasn't caused any insurmountable problems. Regardless, there's a C variable for each and every memory location referenced by the program, named exactly the same as the RE, generally of the same type & size (though sometimes optimised), and in ascending memory address order. This may be an important implementation detail.

Likewise, functions mirror those in the RE and have identical names, and are defined in the same order as in the original code. I even preclude each with a comment containing the ASM address for reference. The main difference is that functions may have arguments - typically but not limited to pointers and data values passed in CPU registers in the ASM code - and sometimes even a return value.

These practices make it easier to reference functionality back to the ASM source, either during development or perhaps at a later date by someone studying the implementation of the original code.

Over the course of the last few projects I've refined the process incrementally. Significantly, I'd start with the RE source file and prepend each and every line with a C++ comment. Effectively, the file would then compile to nothing. I'd then start the transcode by adding C functions at the corresponding lines in the file, just below the ASM routine, referencing the code as I wrote the C equivalent. Once each function was completed and debugged, I'd actually delete the ASM routine from the file. End result is pure C code.

At some point during the Scramble transcode, I found it useful to include snippets of the commented ASM code to document what I'd done and/or why I'd done it that way. Or to preserve unused routines or data for future reference. By the end of the transcode, I was considering that perhaps it would be advantageous to leave all the original ASM code in the file, even interleaved within blocks of code in the functions, to provide a definitive reference. And to possibly assist in identification of bugs in the transcode should they become apparent only some time after the fact.

And so for Galaxian, I think I'll start out on this route. By prepending with a C++ style comment and some obscure but visually subtle sequence of characters, the possibility to completely strip the ASM code from the C code with just a few keystrokes would be retained.

Hopefully what I've described here will become more clear when we get into the transcode and I can provide a few concrete examples of transcoded data variables and functions.

Next up, in what will likely be the last part before we get to the transcode proper, I'll detail the process of testing the framework in readiness to start on the real work.

Monday 30 August 2021

Galaxian Part 4 - Rounding out the framework for a port/transcode

Some of what I'll discuss is more pertinent to C than it is to Javascript. But there's no reason why you couldn't adopt the same architecture in Javascript anyway. In fact, it may still be a good idea for a couple of reasons, including future porting, or even enhancement much like the 'DX' emulations popular for a brief amount of time a ways back.

The main aim is to produce a C transcode of Galaxian core logic that is completely platform-independent; in this case a single C source file with just two entry points - the main function galaxian (), and the interrupt handler galaxian_nmi () - that can be included verbatim on each and every host platform port, whether that be PC, Neo Geo, Amiga etc. Not unlike the MAME driver, for example.

Coupled with this are a set of functions that are implemented in an OS-dependent file that are called from the core source file above. The functions include hardware I/O for reading dipswitch settings, control panel inputs (joystick, buttons) and miscellaneous hardware such as interrupt enable/disable, sound registers etc and of course graphics (display) output. Again, much like the MAME driver.

Here's a breakdown of the source files in my Galaxian project:

  • galaxian.c/h
  • galaxian_gfx.c
  • debug.c/h
  • galaxian_osd.h
  • main.cpp
As discussed, galaxian.c/h implements the platform-independent Galaxian core transcode with galaxian () and galaxian_nmi () entry points. The header contains prototypes for these two functions, plus any data structures or defines that are also required in the main.cpp file.

The galaxian_gfx.c file is the output of the graphics conversion utility discussed in Part 2 and referenced by main.cpp only.

The debug.c/h are simply auxiliary files used during development to implement any debug functions that may assist in the process. These may include functions to dump data structures for example.

The galaxian_osd.h header file contains prototypes for functions implemented in main.cpp. There is only one version of this file and is included on all host platforms.

Finally, main.cpp is where all the platform-dependent code is implemented that actually makes things happen on the host platform. Here the environment is initialised, the framework initialised (create timers and threads, for example), inputs like the PC keyboard mapped to Galaxian hardware I/O addresses, outputs like Galaxian hardware I/O addresses controlling sound mapped to host sound functions, and finally memory mapped areas like video attribute & tile data and sprite registers hooked to host graphics functions. This is the one and only file for which each host platform has its own version!

Since main.cpp is where everything is initialised and where the core is connected to inputs and outputs, most of the functionality is likely to be implemented fairly early-on in the transcode. Host platform initialisation aside, the graphics output is going to be the first functionality that is coded, because we want (need) to see what's happening in the Galaxian code as we transcode. Whilst we can ignore, or at least defer, other I/O like sound outputs and dipswitch or control panel inputs, it's likely we'll at least have to implement some stub code to return default (inactive) input values.

So for the bulk of the time (and effort) in this project, the only file we need to work on is galaxian.c.

For the most part, the core code directly interacts with the host platform hardware when it calls into the OS-dependent (osd) functions. For example when reading from the joystick input port, the osd function in main.cpp will read the state from the PC keyboard, translating back to port bits for the core to manipulate. Similarly when muting sound output for example, the osd function may toggle host sound on/off depending on the state of a control bit written by the core.

For the video though, we may not necessarily want to do this. We may wish to, for example, synchronise host video updates with the host VBLANK as provided in host graphics libraries.

As discussed in Part 3, the first thing Galaxian does in the NMI is update tile attribute (colour & scroll) registers and sprite registers. This avoids flickering and tearing as the beam is still in the vertical retrace at the start of the NMI. It does this by a block copy from a memory buffer into the tile attribute and sprite register memory space (aka object RAM or OBJRAM).

Writing to OBJRAM requires osd functions because ultimately it's accessing hardware, but there are a few options for exactly what is implemented in the osd function, and to some extent depends on the host platform and how the framework is implemented. I've chosen to simply store it in an intermediate buffer which is subsequently used to update the tiles and sprites on the screen elsewhere in the NMI handling of the framework, for maximum flexibility.

Similarly, writing to the tilemap (character RAM aka CRAM) also requires an osd function and again I've chosen to write it to an intermediate buffer and update the host display elsewhere. However I would note that Galaxian does not update the CRAM during VBLANK, so it would be perfectly acceptable to have the osd function update the host display then and there. There's no right or wrong solution here.

So that's pretty much all there is to the framework. Ideally we have a single core Galaxian C implementation and just a single cpp file that must be implemented for each host platform that for the most part handles inputs and graphics. Porting to subsequent hosts should be almost trivial!

Next I'll discuss some of the main aims of the transcode and the general architecture of the galaxian.c implementation. I've actually found my transcodes to be an evolving process and as a result of my most recent Scramble project I'll be tweaking again it for this project.

Sunday 29 August 2021

Galaxian Part 3 - Anatomy of an arcade game

Before we can create a framework for the transcoded game, we need to understand the architecture of the original.

Every single arcade game that I have reverse-engineered thus far has been driven by the 60Hz VBLANK interrupt. Not only does this allow the game to update the sprites without any flicker or tear, it also serves as a reliable, periodic timer on which to base the speed of the game. Galaxian is no exception.

So in a game like Galaxian, there are two contexts of code execution; the 60Hz VBLANK interrupt, which is wired to the Z80's NMI and causes the CPU to execute code at address 0066h, and the main code which starts executing at address 0000h when the CPU is reset and runs (continues) after the aforementioned NMI handler routine is finished until it fires again.

Aside from various hardware functions, the NMI handler contains all the processing in the game that affects, and is affected by, the speed that the game is running. This would include things like player movement, enemy AI, player shots and bullets etc. Typically the NMI would also update the physical display, handle in-game timers, control sound and debounce inputs for example.

As for Galaxian specifically, it does the following:

  • updates the tilemap attributes (colours and scroll registers) and sprites from a memory buffer
  • reads and debounces player control inputs
  • updates a system timer variable
  • checks for and handles coins
  • updates sounds
  • scrolls text, and finally
  • runs the game state machine(s); handling title screens, attract mode and game mechanics such as AI, movement, bullets, exploding etc and in the process updating the memory buffer with tile attribute and sprite data for the next screen refresh (NMI).
So what is left for the main code to do then? In Galaxian the main code performs functions such as printing text, scores etc on the screen, and also removing aliens from the swarm as they dive, and adding them back to the swarm if they return. The main code does all the updating of the tilemap contents (the actual characters you see on the screen).

In Galaxian there is a command queue, implemented via a circular buffer, that is written by the NMI and read by the main code, which allows the NMI to, well, queue a number of commands to be performed by the main code between interrupts. For example, whenever a player shoots an alien - which is processed by the NMI - the NMI can queue a command to update the player score.

It may be worth noting here that Galaxian assumes that there will always be sufficient time for the main code to empty the command queue before the next interrupt, as there is no concurrency protection around the queue pointer manipulation in the main code. Not so much an issue in a controlled environment such as the Galaxian arcade machine hardware; a different matter perhaps on a modern, mutli-tasking platform.

I could say plenty more about the architecture of the Galaxian code itself, but I've detailed enough to put together a framework for a transcode/port on another platform. Simply put, executing the game should ultimately end up executing the main code, whilst a periodic 60Hz timer/interrupt should execute the NMI code. Ideally, the NMI should (also) suspend execution of the main code just as on the Z80, but it's also possible to minimise (but not eliminate altogether) issues when prototyping by use of a few mutexes for the command queue processing. I learnt this lesson during the Scramble transcode.

For my prototype on the PC, my main function sets up the environment under Allegro, including all the graphics as mentioned in Part 2), and a 60Hz timer with a callback that subsequently calls the NMI code in the transcode, galaxian_nmi(). It then creates a thread that calls the main function in the transcode, galaxian (), and finally starts the timer.

Next I'll round out discussion of the framework, including handling hardware I/O and video updates.

Saturday 28 August 2021

Galaxian Part 2 - Graphics formats

The format of the graphics in an arcade game like Galaxian is optimised for the video circuitry rendering the image as the raster beam scans down the screen.

Specifically for Galaxian, the graphics data for each bitplane is stored in a separate device (ROM chip) so that colour data for each pixel can be accessed in parallel, and data for one line of each (8x8) tile is contained in a single byte from each device. Additionally, Galaxian has a vertical monitor, which means pixel ordering differs from, for example, a PC with a horizontal monitor; a straight dump of the graphics data results in the images rotated 90 degrees and in some cases, also mirrored.

This of course isn't very convenient at all for a machine like a PC that may be emulating the game. Here is a snippet of the macro-filled array that MAME uses to construct tile data from the original graphics ROMS:

static const gfx_layout galaxian_charlayout =
{
8,8,
RGN_FRAC(1,2),
2,
{ RGN_FRAC(0,2), RGN_FRAC(1,2) },
{ STEP8(0,1) },
{ STEP8(0,8) },
8*8
};

There's little point going into an explanation here, but suffice it to say it's not an ideal starting point to convert the graphics into whatever format the host language/environment/platform requires.

Arguably the easiest format to start with is a 1 byte-per-pixel (bpp), pre-rotated, array in C. From there, it is much easier to convert to another format, whether that be rendered directly into memory bitmaps for the Allegro graphics library, or re-arranged into binary ROM images for the Neo Geo.

And so, first order of business was to rip the graphics from the Galaxian ROM images into the abovementioned C array. Once for the 8x8 tiles, once again for the 16x16 sprites - both of which are stored in the same ROM (we have several orders of magnitude more space to work with on modern machines). A small C program that outputs a C source file did the trick. At the same time I also added the colour PROM data, converted to platform-independent RGB values.

Here's what the output for a tile looks like:

uint8_t tile[256][64] =
{
  // $00
  {
    0x00, 0x00, 0x00, 0x03, 0x03, 0x03, 0x00, 0x00,     //    ###  
    0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x03, 0x00,     //   #  ## 
    0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x03, 0x03,     //  ##   ##
    0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x03, 0x03,     //  ##   ##
    0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x03, 0x03,     //  ##   ##
    0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x03, 0x00,     //   ##  # 
    0x00, 0x00, 0x00, 0x03, 0x03, 0x03, 0x00, 0x00,     //    ###  
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,     //         
  },

Note that I added the 'ASCII art' rendering of the tiles to both serve as documentation, and a sanity check that the conversion was correct.

Since I am prototyping the transcode on the PC using the Allegro graphics library, I need simply to incorporate the output of my conversion utility into the build and the raw, platform-indepedent graphics data is included in the program. And in this particular case, the startup code for the Galaxian port will read the graphics array, initalise the palettes and render the tiles & sprites into memory bitmaps to be blitted to the screen at run time.

I'm hoping that Miro will be able to use a similar technique for Javascript, in which case I'll tweak my utility to (also) output a Javascript source file. If not, Miro should at least be able to write a small utility to convert the same data into something he can use to render the graphics.

For porting to systems that read graphics data directly from ROMS for example, that C file would be included in a small utility that produced the respective ROM file images. This is something I did for Scramble on the Neo Geo.

So now we have all the graphics data in a source file that can be rendered - directly or indirectly - onto the host platform display. Exactly what we need to be able to check the progress of the transcode.

Next step is to get a project and software framework for the game up and running, and I generally write some test code to render all the tiles and sprites as a sanity check.

Galaxian Part 1 - Introduction

A fellow retro enthusiast by the name of Miro expressed interest in the porting (transcoding) of Sean Riddle's Galaxian dissassembly to a higher level language and asked if anyone was willing to collaborate. Given that the codebase is very similar to Scramble, and the hardware identical, I thought I'd be a decent candidate for the job. I know the Scramble starmap isn't finished on the Neo Geo, but I figured since I'd need to do it for Galaxian anyway, there would be no harm starting this new project.

I'm going to try to document more of the process this time around, rather than focus on progress. I'll still write about any interesting aspects of the code, or issues with the transcode, but I'm hoping this will serve as a reference/instruction for Miro and anyone else interested in giving something like this a go.

Miro's expertise is not in Z80 assembler (the point of the exercise for him) or C, so he will be porting/transcoding to Javascript. Given I know very little about Javascript, I'm hoping my process is compatible with what he needs to do. It'll certainly be an interesting experiment.

I'll be able to start with the bones of the Scramble project. In fact, for me it's pretty much a case of renaming a few files and high-level functions, ripping new graphics, and replacing my monolithic transcode of Scramble with that of Galaxian. And tonight I did just that. My makefile now builds both Scramble and Galaxian executables; it's just that Galaxian looks very much like Scramble atm - I'd even go as far as to say identical! ☺

In my next post I'll talk about ripping the graphics and the format I've chosen.


Friday 9 July 2021

Oh yeah, Scramble...

Well, once you lose momentum on a project when Real Life intervenes, you can very quickly forget about it completely, which is pretty much what happened on Scramble. I must admit, I haven't even thought about it for several weeks now, only reminded about it now by a 68K post on social media.

I do intend to get back to it. My workload (at work) has far exceeded initial estimates but there is now light at the end of the tunnel. I haven't even been playing anything lately! But soon I hope to get back to retro projects again.

Saturday 29 May 2021

Scrambling for time atm!

Stars are still WIP, I've been coding the algorithm to produce the tile data and also the data for optimal palette cycling. However work has gotten very busy (plus the Giro d'Italia is currently running) so I haven't done a lot lately. Work will still be busy for another work or two, but then it should quieten down and I'll knock off the last piece of the puzzle.

I haven't put it aside and am keen to start on Galaxian next...

Wednesday 19 May 2021

A star is... coded.

 So I have a plan for the stars.

There are 124 stars, and using 16x16 tiles would require 103 tiles to contain them. Tiles have no more than 3 stars in them. Tiles will also have their own palette (16 colour entries).

There are 4 blink cycles. Rather than use 4 layers of sprites, I plan to use just one layer, with all stars rendered, and use palette cycling to turn stars on and off. At most, the states of 61 stars change in a blink cycle, though some of them may be on the same tile. So worst case, I need to update 61x3=183 words in the palette map. That should still be pretty insignificant as far as processing time for a frame.

In theory, I can replicate the exact star pattern, colours and behaviour - at least as implemented in MAME.

Just need to finish off writing the code to generate the tile and palette data, and some extra data to assist in optimising the updates.

UPDATE: After revisiting the star generator in MAME, it seems worst case is only 47 stars changing state, which is 47x3=141 words written. So a bit quicker again. Tile generation code next...

Tuesday 18 May 2021

Star gazing

Have to admit I had a few days off doing much on Scramble. I did a little with non-tate mode on the Neo Geo but not enough to have anything worth blogging about.

Curiously there is some sort of 'glitch' in the rendering but I don't even understand the nature of the glitch at this point so I can't explain it. It manifests itself as an occasional dark flicker in a bottom tile of the landscape, which only happens whilst the draw_landscape() function is executing. But the landscape is only ever drawn off the visible area to the right, and once scrolled into the screen, is not updated. Furthermore, if I remove the code that clears the columns before rendering, the flickering seems to disappear. So I'm more inclined to conclude at this stage that it's specifically related to the Neo Geo implementation rather than the race conditions I saw earlier.

Moving on from there, I've been looking at the implementation of the stars in MAME. I should be able to mimic the distribution and the colour set; the precise blinking pattern may be another matter. It does cycle through 4 patterns of different stars being enabled/disabled. I think a close approximation will suffice.

It'll be a matter of generating a set of 16x16 tiles with the star patterns and using palette cycling to control them, perhaps even to enable/disable them as well. Like the tilemap, the stars will require a set of chained sprites in a fixed location. And being at the back in the priority order, I'll have to shuffle the existing sprites up a bit.

That leaves tweaking sprite positions, fixing Neo Geo-related implementation issues (controllers and coin-up)  and maybe look at sound, before I revisit non-tate mode.

I'm still undecided about attempting an Amiga port just now. I did just see reference to a video setting up gcc for Amiga development. I'm no Amiga expert so not sure how difficult scrolling a portion of the display is... but maybe that's a good excuse to learn.


UPDATE: I've fixed sprite and bullet position relative to the tilemap. Still have to tweak tilemap position on the Neo Geo though, so will likely require more tweaking. However I think I've got a bug in the bullet/rocket collision detection routine; occasionally the bullets appear to pass straight through rockets.

UPDATE #2: Had a quick play this morning and can see there's definitely an issue with bullet collision-detection at least, and it's not limited to rockets. Occasionally the bullet will pass straight through objects. A quick look at the code doesn't give any hints.

UPDATE #3: Had a look at the star generation for Galaxian in MAME. Scramble is slightly simpler as it doesn't scroll, but mapping the hardware implementation to the software framework of MAME convolutes the algorithm somewhat. In short, an LFSR generates the star positions and colour index (from a palette of 64), whilst further combinatorial logic controls the 4 cycles of blinking. There are a total of (just) 124 stars on the screen, not all visible at the same time due to the blinking effect.

All 124 stars generated for Scramble

I have actually implemented the same LFSR in the code to generate the star tiles for the Neo Geo, but there are a few unsolved issues that remain. Sprites are limited to 16 colours, whilst stars have 64. There are a number of approaches to that issue, including (effectively) assigning each star its own palette. Then there's the blinking; do I use palette cycling, or have 4 layers of sprites? Food for thought...

Saturday 15 May 2021

Running at full tilt!

Great result! By updating 96 entries in the (memory-mapped) palette map rather than the palette index in 1024 (effectively port-mapped) sprite registers, the entire ISR now runs for signifcantly less than 1/4 of a frame and the landscape drawing is finished well before the next VBLANK interrupt.

Palette update finished before the visible area of the frame is rendered

This gives plenty of head-room for the code to run and any potential race conditions should now be alleviated. The game should run at 100% now with no glitches.

One thing I did (also) change is move the scrolling to the start of the ISR (before palette change) to eliminate the screen tearing I noticed on MAME. I'll do the same for the sprites.

And the background colour; I was thinking I'd need another layer of sprites but of course you can control the background colour directly on the Neo Geo (as I have been doing to profile the ISR) so that was a one-line implementation!

Blue background. All that is missing are the stars


Next up stars (which will need their own layer - just another 32 sprites and some tiles created for stars) and maybe use palette to blink them.

Finally, I need to tweak some Neo Geo-specific features, such as coining up and playing a game. At the moment I can only read the SELECT button running as an AES. Not sure how to handle coin-up on MVS just yet as it's handled by the BIOS depending on what mode the game is running in. If I get time today, I'd like to copy the ROM to my NeoSD and try it on my real AES console!

UPDATE: Scramble running on my Neo Geo AES!

Washed out colour due to the phone camera/flash


Profiling and palette manipulation?

I was thinking more about the glitches I saw and race conditions in the code which also reared their heads in the prototype PC port. There are two areas of the code which could, under certain conditions, cause issues. The one relevant here occurs when the mainline code is (still) drawing the landscape and gets interrupted by the next VBLANK. Not that this should never happen on the original machine.

So a few profiling experiments tonight to see how much time is spent doing what.

I thought of using the background colour to indicate which area of code was being executed. I was shocked to see how much time was spent in the ISR - not actually running Scramble code but updating the Neo Geo display hardware or, more specifically, the tilemap palette (there's 1024 tiles to update every frame).

The background is set to RED in the ISR during the video hardware update, YELLOW when the Scramble ISR code is running, and BLUE when the mainline code is updating the landscape (drawing the right-most 2 columns).

Everything enabled

As you can see, it spends about two-thirds of the time in the ISR, and more than half of that is just updating the video hardware (red). When the ISR queues a few commands to be executed by the mainline code, the landscape draw function (blue) extends to the right-hand edge of the screen. This is where you'd see glitches before I disabled interrupts around the VRAM accesses.

I halved the number of tilemap palette updates and saw a correspondingly shorter red area. And after eliminating all tilemap palette updates, the red isn't even visible which means the (Scramble) sprite updates are taking very little time at all.

Updating only half the tilemap palette

Although the blue area sometimes moves a little towards the right-hand edge, I never saw it actually get there. What this tells me is that there's sufficient time to execute all the Scramble code on the 68K; it's just the VRAM accesses that are causing issues. So there's little point trying to optimise the generate ASM code any further I don't think.

So what can I do from here? Ideally the landscape drawing (blue) should be finished before the next VBLANK interrupt, otherwise it's possible some object can be drawn in the wrong column - even with the interrupt protection. That's because the ISR changes some variables used by the landscape drawing function.

One option is to update the palette only when the palette actually changes, and only for those tiles that have changed, rather than each and every tile every frame. But that doesn't actually change the worse-case scenario, and will only reduce the problem, not eliminate it. And the extra code could push the landscape update further into the next VBLANK.

The landscape draw function is also surprisingly slow, though it requires quite a few VRAM accesses as well. I could take a copy of the variables updated in the ISR at the start of the function which means it would only be critical that the ISR start before the next VBLANK...

Something I need to ponder further... feel free to provide suggestions.

Actually I just had a thought - how about updating the palette entries instead of updating all the tilemap entries!?! This might have merit... stay tuned!

UPDATE: I'm yet to try it, but I think this will work! On Scramble there's a colour (palette index) for each column (32 tiles) - so 32 index entries. I can simply assign each of the columns to its own Neo Geo palette, and then update the three words for each column every frame - a total of 32x3=96 writes and no VRAM address register slowing it down ever more!

Friday 14 May 2021

So it's down to looking at gcc code generation...

The good news is that I've sorted all the issues with race conditions. I learned how to generate extended inline ASM in gcc, refreshed my memory on Neo Geo interrupts, took a look at the ngdevkit crt0.S code, rediscovered how to generate assembler and symbol listings in gcc, and confirmed the findings in the MAME debugger.

In a nutshell, all I had to do is disable the VBLANK interrupt in the function that updates the tilemap, where it sets REG_VRAMADDR and then writes the tile number to REG_VRAMRW. This eliminates all the glitches where tiles would occasionally be written to the wrong location on the tilemap.

Note that this function is (also) called from the VBLANK interrupt, so I had to save/restore the 68K status register rather than simply re-enable interrupts.

The only other critical function that accesses the Neo Geo VRAM is called from the ngdevkit VBLANK rom callback routine, after the interrupt has been acknowledged (so I don't have to do it after all) and in the context of an ISR (interrupts are already disabled). So nothing to do there.

I also did a preliminary optimisation of the code that updates the tilemap attribute (colour) and scroll registers, and all the sprite registers. On the original arcade hardware, this is done in the NMI and is a simple 128-byte LDIR. Not so simple on the Neo Geo, and quite slow with the REG_VRAMADDR mechanism.

So onto the bad news.

The NMI (VBLANK interrupt) code is taking too much CPU time and although it does finish before the next interrupt, the mainline code is starved of CPU. So you can coin up and play the game but tasks like updating the text and so-called head-up display, and drawing some of the landscape isn't being done.

Taking a look at some of the code being generated was an eye-opener to say the least. There are snippets of code that are inexplicably inefficient and complex. It seems that any code involving a structure member variable is just ridiculously inefficient, and that's a static structure so there's no member offset calculations involved at all.

Check out this snippet that contrasts a simple member update...

1604:src/scramble/scramble.c **** wram.score_table_text_ptr = score_table_text;
4554 .loc 1 1604 0
4555 1d86 203C 0000 move.l #score_table_text,%d0
4555 0000
4556 1d8c 2200 move.l %d0,%d1
4557 1d8e 4241 clr.w %d1
4558 1d90 4841 swap %d1
4559 1d92 E049 lsr.w #8,%d1
4560 1d94 1439 0000 move.b wram+1113,%d2
4560 0000
4561 1d9a 0202 0000 and.b #0,%d2
4562 1d9e 8202 or.b %d2,%d1
4563 1da0 13C1 0000 move.b %d1,wram+1113
4563 0000
4564 1da6 2200 move.l %d0,%d1
4565 1da8 4241 clr.w %d1
4566 1daa 4841 swap %d1
4567 1dac 7400 moveq #0,%d2
4568 1dae 4602 not.b %d2
4569 1db0 C481 and.l %d1,%d2
4570 1db2 2F42 000C move.l %d2,12(%sp)
4571 1db6 1239 0000 move.b wram+1114,%d1
4571 0000 4572 1dbc 0201 0000 and.b #0,%d1
4573 1dc0 822F 000F or.b 15(%sp),%d1
4574 1dc4 13C1 0000 move.b %d1,wram+1114
4574 0000
4575 1dca 2200 move.l %d0,%d1
4576 1dcc E089 lsr.l #8,%d1
4577 1dce 7400 moveq #0,%d2
4578 1dd0 4602 not.b %d2
4579 1dd2 C481 and.l %d1,%d2
4580 1dd4 2F42 0008 move.l %d2,8(%sp)
4581 1dd8 1239 0000 move.b wram+1115,%d1
4581 0000
4582 1dde 0201 0000 and.b #0,%d1
4583 1de2 822F 000B or.b 11(%sp),%d1
4584 1de6 13C1 0000 move.b %d1,wram+1115
4584 0000
4585 1dec 7200 moveq #0,%d1
4586 1dee 4601 not.b %d1
4587 1df0 C280 and.l %d0,%d1
4588 1df2 2F41 0004 move.l %d1,4(%sp)
4589 1df6 1039 0000 move.b wram+1116,%d0
4589 0000
4590 1dfc 0200 0000 and.b #0,%d0
4591 1e00 802F 0007 or.b 7(%sp),%d0
4592 1e04 13C0 0000 move.b %d0,wram+1116
4592 0000

  ...with that of a discrete static variable:

1605:src/scramble/scramble.c **** bananas = score_table_text;
4593 .loc 1 1605 0
4594 1e0a 23FC 0000 move.l #score_table_text,bananas

That's quite a difference! There are other instances throughout the code, even for simple uint16_t variables. I'm sure it all adds up. So one option is to move all the variables out of the structure and see what sort of savings I get.

The not so bad news is that it seems to not be too far from the tipping point as-is. If I remove the code that updates the tilemap attributes for example, which comprises a mere 32 writes to VRAM, then the game appears to run just fine.

So a bit of experimentation in optimsation still to be done.

UPDATE: The structure issue could rather be an alignment issue... more to come...

UPDATE #2: Removing #pragma pack(1) from the wram structure produced code akin to the 2nd snippet above. It also decreased the code size from 0xB28C to 0x98F0. However it didn't seem to have any effect on the NMI execution time... hmm...

UPDATE #3: Almost there! Some more optimisation of the tilemap update code together with eliminating most of the structure packing and the game is almost glitch-free. Occasionally on the ufo stage the landscape isn't drawn correctly (more on this later).

Thursday 13 May 2021

About to bite the bullet...

It's time to book the fat lady who sings. After consulting my Donkey Kong code, I copied the "magic" algorithm for transforming the sprite Y coordinate and viola - sprites appeared! Still perhaps not pixel-perfect due to the placement of the underlying tilemap, but close enough for now.

Score table with sprites

It runs slow, there are glitches, but most of the implementation is done. Bullets are missing but everything else is in place. It's actually playable now.

Scramble, on the Neo Geo in tate mode

The game really slowed down when I got the sprite update code working. Not sure why because there wasn't a lot I added but maybe just enough to tip the NMI execution time over the 16ms mark. It could also be because of the brute-force hack I did to minimise glitches (still) caused by a race condition with the Neo Geo VRAM registers. So there's still a bit of optmisation that can be done. I have no doubt I can ultimately get it running at 60fps.

The scrolling was trivial to implement on the Neo Geo. Initially I defined the tilemap as a single 32x32 tile (scaled to 50%) chained sprite, with a single origin. But today I divided it into 3 chained sprites, the middle sprite being the scrolling portion of the display. So scrolling the playfield now requires an update to just a single sprite Y coordinate register - too easy.

I'll need to go back and create a tile for the bullet sprite; on the arcade game the bullets are not rendered from ROM data like other sprites but rather a dedicated circuit on the PCB. Then I can add the code to assign another 4 Neo Geo sprites to the bullets and the functionality should then be complete.

From there it's a matter of handling the race conditions (I think I just need to disable interrupts briefly) and optimise the VRAM access code and that should be it. I might look into adding sound if I can find some samples as there seems to be resources around to get a sound driver up and running.

I'm also thinking of options and strategies for rotating the display. I already have rotated tiles and sprites in the CROM.

UPDATE: added bullets.

My progress has been... interrupted.

After such rapid progress early on it has all come to a crashing halt. I've spent way too much time attempting to get sprites displayed, only to make matters worse. I did, however, manage to fix the background colour and get the tilemap palette working.

Proper tilemap palette

There seems to be either some corruption on the tilemap, or some sprites that haven't been cleared that are causing issues. The attract mode text screens are minimally affected but when the demo/game starts, all hell breaks loose.

Looks OK except the missing '0' in the high score

In theory the sprites should be almost trivial, especially given the tilemap was relatively straightforward. I'm using the same technique I used in Donkey Kong almost a decade ago; the tilemap consists of 32 vertical sprites each 32 tiles high, with sticky bits set. And the sprites are, well, just single-tile Neo Geo sprites.

I have discovered what I strongly believe is a contribuing factor. But right now, I don't know the best way to solve it. It's actually going to be a little horrible, I think.

The Neo Geo has a VRAM address register (REG_VRAMADDR) that determines where in memory you write VRAM data. Routines that update the tilemap and the sprites need to set this register before any VRAM accesses. The trouble is, these routines are called from both the main code, and the NMI, which means the NMI can preempt the call from the main code, changing REG_VRAMADDR and causing the wrong address to be subsequently written.

One fix is to mask the interrupt whenever the code is writing to VRAM, though I'm not sure if it's even possible on the Neo Geo. Another fix would be to shadow all writes to VRAM and have the NMI write them through to the hardware - in fact I think that's what I'm doing on Donkey Kong.

But there also seems to be a similar issue with the command queue - implemented as a circular buffer - which is pushed by the NMI and popped by the main code. I can only foresee an issue though, when the queue is actually full; so maybe right now the background is getting no CPU...

Either way, some ugly issues to solve.

UPDATE: All is not as bad as it seems! I wasn't ACKing the VBLANK interrupt properly! Still some work to do, and still no sprites, but looking much better already!

Coined up and playing the game!


Wednesday 12 May 2021

Finding my way around ngdevkit

I spent some time this morning trying to build Scramble with my old makefile to no avail. I'm yet to understand how pkg-config actually works but I wasn't able to resolve all references and there were also differences in the startup code and vector table for the cart. Ideally I want a common makefile for all targets but the investment in time and effort isn't warranted at this point.

The built-in eye-catcher

As a result I finally cut my losses and started instead with one of the examples included with ngdevkit. I don't understand at this point how to configure certain cart header items like game name and eye-catcher settings, but at least I have a project building now with the Scramble core code and NMI handler running off the ngdevkit VBLANK interrupt call-back.

In theory scramble is running but with no osd layer there's nothing to see. For now all I'm doing is defining the dipswitch for lives, initialising the palette and printing some text on the FIX layer. Not sure where the blue background comes from; it's not in the Scramble palette and it doesn't appear to be on the FIX layer, so...

It's a start...

I've converted the actual palette from the arcade and next up is to create the data for the tiles and sprites in the C1,C2 ROMs. That should be relatively straightforward as I hope to simply adapt conversion code from earlier projects.

UPDATE: Well I can confirm the game is running, including the NMI. Here is a snapshot from MAME with only the tilemap (sans scrolling) implemented, albeit with a funny palette. Note that the Neo Geo display is rotated using the -rol option. I must admit I didn't expect to get this far so easily.

Scramble running on the Neo Geo in MAME


ngdevkit to the rescue!

I've done about as much as I can do on the PC platform, and added the map generator, so I started to look at the Neo Geo port.

I've been using the old NEODEV toolkit which is based on the rather ancient gcc 2.95.2 for my past projects (Lode Runner, Knight Lore). It has its quirks but generally works pretty well. However it doesn't support the newer C standards whose features have inevitably crept into my Scramble code as I haven't developed for it for years, and an initial compile threw up literally hundreds of errors.

I started to fix the errors but then wondered whether there was a more modern option for building Neo Geo software in C. I came across Damien Ciabrini's ngdevkit which is (currently) based on gcc 5.5.0 and looks the business, shipped with several examples that build out of the box and run under MAME. Admittedly it took me a while to build the windows (MSYS2) toolchain correctly, but when I actually followed all of the instructions, it built and installed without a hitch.

The example project makefiles bear absolutely no resemblance to my old Knight Lore makefile - so learning a new way to do things will be interesting - but with some quick hacking of my old makefile, compiling the scramble core module actually results in zero errors, so I may yet get away with my anonymous unions and structures...

Of course I can't generate a ROM file just yet, as the operating system dependent wrapper hasn't been modified for the Scramble core, so that's a task for next session. But it's very promising that I've gotten this far with so little effort.


Tuesday 11 May 2021

Preliminary map.

As usual not a lot of time on the weekends and today (Monday) was no different. I did however have a chance to play around with some map generating code.

Here's a preliminary map. I need to add background colour and I'll also throw in some stars just to indicate where they are/aren't visible. I should add level markers and I could also add the ufo and fireballs to their respective areas. I might also throw in some counts of the various objects, and maybe total points value.

Preliminary map of Scramble

It's 1,430 characters or 11,440 pixels wide (approximately 51 screens) including the flat strip at the start which technically isn't part of the landscape meta data, but rather tacked on to the start of each area when you're starting over with a new ship. The landscape scrolls 1 pixel every VBLANK interrupt and the colour changes every 512 interrupts/pixels. There are 7 colour palettes for the landscape (colour set 1 isn't used for some reason, although it is cycled in an explosion).

UPDATE: Added stage markers

Added title screen text and stage markers

UPDATE #2: Added background colour, stars, ufos and fireballs

Release candidate



Friday 7 May 2021

Scramble the movie

 All bugs (AFAICT) have been fixed, transoded C listing has been tidied up, and I'm ready to start on the Neo Geo port next session.

There'll be a bit of a ramp-up as I haven't touched Neo Geo development for a few years now. But I should have enough examples from my past projects to get things up and running relatively quickly.

For now, a video of the C code in action on the PC. Scaling x2, but bullets not scaled properly.


Still haven't quite worked out how the scrolling will work on the Neo Geo, but I'm sure I'll figure it out. Plenty of scrolling games on the Neo Geo...

Go big or go home!

 The two bomb-related bugs were related, and they've been fixed.

In the mean-time, I was getting sick of squinting at the small screen and thought I'd look into how difficult it would be to upscale the display on the PC. Turns out it was trivial (except for the bullets) using the Allegro5 bitmap scaling functions and the whole process, including research, took about 10 minutes.

Scale factor x2

It actually looks more accurate using 4:3 scaling but again, the point of this exercise is not about playing it on the PC.

So this last bug has me really scratching my head. The symptoms are (mostly) missing vertical strips from the landscape. Occasionally the strips will filled with erroneous landscape graphics, rather than being missing (black)...

Vertical bars missing from the lndscape

The odd thing is, although it happens quite frequently, it only seems to happen after you crash your ship into the landscape. It doesn't happen if your ship is destroyed by any in-flight enemies such as rockets.

I do know that it's not corrupting any ROM data structures (in this case static C arrays), because subsequent plays do not exhibit the corruption. That would suggest perhaps it's a corruption in RAM. But I have seen the case where it renders the flat landscape correctly, only to have it (the flat part) corrupted as soon as it starts scrolling. Once rendered, landscape should not be rewritten until it scrolls off the screen.

That left me wondering whether it was a bug in my Allegro-layer routines. But the fact it only happens when you hit the landscape doesn't quite fit with that theory either. But typing all this out has got me thinking...

UPDATE: I've just confirmed it's the character RAM (tilemap) that is being corrupted. I suspect the code to render two columns right of screen is writing to the wrong area, either because there's a bug in the routine itself, or some area of RAM is being corrupted or not initialised properly (more likely).

UPDATE #2: Not just landscape collisions that cause the issue.

UPDATE #3: Solved! That was a tough one. Turned out to be a race condition between the main routine and the NMI only because in my prototype implementation I don't have a pre-emptive NMI but rather just 2 threads running simultaneously. I was thinking that the landscape was drawn from the NMI, but I was wrong. I noticed a pointer was changing in the draw_landscape routine between the start and the end of the function and then it twigged.

Now just a little more play testing before I can announce the transcoding is officially done!



Thursday 6 May 2021

Transcode complete; 3 bugs remain (that I know of).

I can now say the transcode is 100% complete in that all routines have been coded and all data transcribed. The only things missing are actual audio output and background stars.

Blue background supported

Sound routines have been coded short of banging hardware to communicate with the audio CPU. Of course on a port those routines will be highly dependent on the audio implementation chosen for that platform. I suspect that will consist of another process playing audio samples.

I fixed a few bugs today including the segmentation fault as I mentioned in the previous post. Three (3) bugs remain that I have seen; some bombs freeze mid-air until you drop a subsequent bomb, some bomb explosions freeze, and occasionally - much less freqently now - there are strips of landscape missing.

Of course my next task is to continue play testing one- and two-player games and finding and fixing these bugs. There's also some minor sprite alignment issues (with bullets for example) that will require some trial-and-error tweaking.

I've got a few build options in the code to facilitate testing such as invincibility, infinite fuel, starting level etc. So I already know playing through and destroying the base starts a new mission just fine.

Once I've fixed the bugs and thoroughly tested I can start on the Neo Geo port...

Wednesday 5 May 2021

It's Da Bomb!

Lack of updates because of lack of progress only because of lack of time.

I can see the light at the end of the tunnel. You can shoot and destroy all objects - on the ground and in the air - and you can launch bombs, though the latter do not have any collision detection routines implemented as yet.

Shooting and bombing

So it's getting very close now; I'd estimate at the very least 95% complete. Aside from the bomb collisions and associated animations, there's a couple of minor routines like displaying mission flags and checking for extra lives and then just testing of the high score table to go.

I haven't implemented any of the sound routines, except adding calls to sound routines wrapped in a macro that nulls them out. The missing routines involve queuing and passing commands to the sound CPU, so there's not actual sound generation done in the code. I will implemement those to facilitate sound on any ports.

Most approriate for any port would, I suspect, be adding 'simulation' of the sound CPU that simply played samples. I'll leave that until later. Likely a lot later.

I haven't added emulation of cocktail mode either, which comprises flipping the screen and reading alternate controller inputs (although the calls are there). There's likely no need for this in any port at all.

I also haven't implemented the starfield or coloured background (blue/black) as of yet. The calls are there in the code, so it's a matter of writing some Allegro code to suit. I just haven't done it yet.

I have recently introduced a rather nasty bug, or perhaps more correctly, triggered it reliably - namely a segmentation fault whenver the player is destroyed and the game is about to restart. I suspect it is related to the landscape corruption I saw earlier. I'll leave this until the transcode is done and hopefully it won't be too difficult to find. Unfortunately I haven't had much luck getting the game to run in GDB thus far.

It'll probably be a few days before I can finish the transcode, sort the segmentation fault and tidy up the rest of the code. There are the odd inefficiences that I've purposefully left in the code, I don't think they'll have any significant impact on the performance on target hardware.

UPDATE: I've added support for the blue background and fixed the segmentation fault. I had a quick look at the MAME source for the stars and I don't think there's any good reason at all to attempt an accurate implementation. In fact not sure I'll even bother with stars on the Allegro prototype, at least not unless I need to prototype something for the other platforms.

There's still a few bugs introduced with the bombs but hopefully they won't be too hard to find. Hope to be able to start on the Neo Geo port in the next few days.


Friday 30 April 2021

The 80:20 rule of Scramble; you can only get 20% of the way through the game with 80% of the code written.

I've completed the game lifecycle mechanics and fixed a few bugs in the process. Somewhat interestingly, in two completely different routines I had omitted the instructions that reset the column scroll values in the C code. Strange coincidence.

But you can now coin up and play 1P or 2P games through to completion and back to attract mode.


2P game in action

There are a few odd bits of code in there, such as specifically copying two one-byte data values from memory (a level value and a flag) over the storage for a pointer; they're never read back and that same copy is done in two places. However it's benign because the pointer is re-initialised before it is used again. I'm scratching my head wondering how that could have gone unnoticed in the assembler source.

I've only got two reasonbly trivial routines that I can't test to go besides all the the routines related to player bullets & bombs and their collisions and explosions. Those routines simply check if an extra life should be awarded (based on score) and also display the 'mission complete' flags.

Looking at the source code after yet another cleanup I'd estimate it to be around 80% complete. There's nothing new in those routines that hasn't been transcoded already, and a lot of it will be cut and paste. Whilst the code is nicely structured and lends itself very readily to a C transcode, there is a reasonable amount of very similar and in some cases outright duplicate code.

From here there's a couple of days of work in the bullets and bombs and then it'll be play testing.

I'm yet to transcode any audio-related routines. The audio control simply involves writing sound ordinal values to the sound CPU (another Z80) which controls a pair of AY8910 sound chips. I'm not intending on porting any audio at this point, but will probably add stubs for the audio routines so that sound may be added at some point in the future, even if not by me.


Thursday 29 April 2021

2P or not 2P, that is the question.

 Today was mostly working through all the game lifecycle mechanics and adding some support for scoring during the game.

In previous transcodes I've just used (long) binary values for scores etc. In Scramble however, at some point early on I chose to preserve the use of BCD, and although I can't recall the exact reason I do remember thinking it was the right choice at the time. I'm regretting it a bit now, because the code for adding to your score is a bit messy in C, and the high score table code was just tedious.

You can now play through a game and get back to attract mode and start another - if you have sufficient credits of course.

1P gameplay mechanics seems to be complete

With no firing/bombing implemented it's difficult to get too far through the game, even with infinite fuel. On the 1st stage it's fairly easy to dodge the rockets, but the UFOs on the 2nd stage are another matter. A simple algorithm but effective.

High score table update is coded but not tested. Interestingly, the way I read it is if your score equals that of any previous entry in the high score table, the game will not insert your new score. That's a bit weird and it's going to be tough to confirm on MAME because playing the game you get 10 pts every second or so just for staying alive.

Although it's boring I'll probably work through the 2P mechanics just to get it ticked off. Given the architecture of the code though, that'll be an effort not considerably less than the 1P mechanics.

I'm wishing now that I had preserved all the ASM code in the completed C functions. It's kind of cool seeing the original code and makes it easier to debug; just blows out the file size considerably.

Given the relative ease of this transcode, and the fact that Galaxian uses the same hardware, I'm pretty keen to start on that as soon as Scramble is finished and ported to at least one if not a few other platforms. I'm a little concerned that older C compilers may not like my anonymous structures and unions, but I'll cross that bridge when I come to it. With Galaxian though, I'll preserve the original ASM code in the C source and see how that works out for me.

Wednesday 28 April 2021

Running on the smell of an oily rag

Working through the game start up and it's a bit of a grind to be honest. A fair bit of code had to be transcribed before anything interesting happened. But I now have the game starting and the player ship moving around. If you don't die from being hit or colliding with something, you'll run out of fuel.

Coin up, start a game and move around.

No shooting or bombing, but you can play through the 3 lives and it displays "GAME OVER" although I suspect the scroll offsets of the display area haven't been reset as they should be as it appears at a random offset from centre, and in the wrong colour. A quick search didn't reveal where that was but I'll find it.

As I work through the code I mark each routine as being one of UNIMPLEMENTED, WIP, LOOKS_OK, UNTESTED or COMPLETE via a printf that can be selectively enabled or disabled by category. Today added a whole swag of UNTESTED routines.

Unlike my earlier efforts, in this transcode I've started with the original disassembly, with every line denoted a C comment. Makes it much easier to keep functions in the same order as the ASM code, and keep track of addresses etc. Until a few days ago I was writing the new C function under the corresponding ASM function, but more recently I've moved the commented ASM inside the functions with the C code following each block of ASM code.

I've been removing the ASM code from the file whenever I deem a function to be COMPLETE (tested, debugged) but now I'm thinking that in my next transcode, I should actually keep the ASM code in there. It'll blow out the source file size of course, but does that really matter?

An example of what I mean...

// $10BA
void game_init (void)
{
  DEBUG_OPT_UNTESTED;
  
  //10BA: 21 09 40    ld   hl,$4009              ; load HL with address of TEMP_COUNTER_4009  
  //10BD: 35          dec  (hl)
  //10BE: C0          ret  nz                    ; wait until counter reaches zero
  //10BF: 36 0A       ld   (hl),$0A              ; set TEMP_COUNTER_4009 to 10

  if (--wram.temp_counter_4009 != 0)
    return;
  wram.temp_counter_4009 = 10;

  //10C1: 2C          inc  l                     ; bump HL to point to SCRIPT_STAGE
  //10C2: 34          inc  (hl)                  ; advance to next stage of script (PLAY_GAME @ $22F4)
  
  wram.script_stage++;
  
  //10C3: 3E 01       ld   a,$01
  //10C5: 32 19 40    ld   ($4019),a             ; set DRAW_LANDSCAPE_FLAG
  
  wram.draw_landscape_flag = 1;
  
  //10C8: 21 01 00    ld   hl,$0001
  //10CB: 22 80 43    ld   ($4380),hl            ; set PLAYERS[0].IsActive and reset PLAYERS[0].IsExploding flags
  //10CE: 22 A0 43    ld   ($43A0),hl            ; set PLAYERS[1].IsActive and reset PLAYERS[1].IsExploding flags
  
  wram.players[0].is_active = 1;
  wram.players[0].is_exploding = 0;
  wram.players[1].is_active = 1;
  wram.players[1].is_exploding = 0;
  
  //10D1: AF          xor  a
  //10D2: 32 82 43    ld   ($4382),a             ; reset PLAYERS[0].StageOfLife (see PLAYER_INIT @ $16FE)
  
  wram.players[0].stage_of_life = 0;
  
  //; player starting level, update lives remaining 
  //10D5: 21 08 41    ld   hl,$4108              ; load HL with address of CURRENT_PLAYER_LIVES
  //10D8: 35          dec  (hl)                  ; reduce number of lives
  //10D9: 11 03 07    ld   de,$0703              ; Command ID: 7 = HEAD_UP_DISPLAY_COMMAND, Param:3 = DISPLAY_CURRENT_PLAYER_LIVES
  //10DC: FF          rst  $38                   ; call QUEUE_COMMAND
  
  wram.current_player_lives--;
  rst38_queue_command (7, 3);           // HEAD_UP_DISPLAY_COMMAND, CURRENT_PLAYER_LIVES


There is one bug that I haven't even looked for that revealed itself after I got the full demo lifecycle coded; when the player is killed and the next life starts, random strips of the landscape are missing. Given the nature of the bug and the data structures in the game, I suspect it's an area of memory being corrupted... hopefully it won't elude me for too long when I finally go looking for it.

There's probably another day or two (at least) working through the game lifecycle mechanics before I get onto the shooting and bombing.