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).
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.
No comments:
Post a Comment