I've identified the main game loop, the keyboard read routines, and commented most (95%) of the movement routines - definitely enough to start porting and see the results.
There's a decent amount of code that needs to be written before I'll see the player moving around on the screen. It's probably not something that can be written in a single session. I also need to properly implement the dual screen logic as it is used extensively in the movement code, for example.
For this reason, I've decided to update the Coco3 (6809) port first. It's a bit simpler than the TRS-80 port since for the latter I'll need a shadow copy in RAM of the port-mapped hires screen, and hence I'm unable to make use of dual-purpose routines. The latter is also slower and more painful than the 6809 port - so much so that I'm actually tempted to put it on ice for the moment.
Next time I sit down to work on this I'll be able to start on the Coco3 port!
This blog chronicles my progress porting various retro games to other retro platforms. The goal in each project - at least when targeting a new CPU - is to effectively replicate the original graphics and the original code line-by-line, to produce a 100% accurate port of the original game.
Tuesday, 6 May 2014
Saturday, 3 May 2014
Spaghetti?
No more porting but I have spent some time on the reverse-engineering.
I've located the player movement (and dig) routines, which I eventually tracked down via the keyboard read call. I'm working through those now and have unearthed a few more crucial variables related to the player tracking throughout the play-field.
It's relatively early days yet, and I reserve the right to retract my statements, but I find some comfort in what I've found so far. Way back some 20+ years ago when I was attempting to write my TRS-80 version, I naively coded the player movement routines by handling each case explicitly in what equated to a rather lengthy case statement. It was classic spaghetti Z80 assembler, and whilst I knew it wasn't elegant, I was too eager to see results to attempt to formulate a more efficient algorithm.
On the surface, that appears to be pretty much how the original code was written. There's a ton of duplicated, cut 'n' paste code related to movement from what I've seen so far; perhaps not as bad as the code I first churned out but certainly nowhere near as elegant as the code that I finally crafted - albeit in its 3rd incarnation - on the PC. At least, it seems to be the case as far as I've gotten at this point.
It's also somewhat reassuring to note that the variables I've managed to identify thus far are heavily used throughout most of the codebase, in fact a surprisingly large percentage of the code seems to be concerned with manipulating these very variables, mostly related to movement (presumably of enemies and the player). If I'm right in assuming the same logic is used for both, then I guess there's little more to the actual game mechanics, other than digging (which should be relatively simple) and enemy AI.
What this suggest is, in stark contrast to a few other projects I've undertaken, that the actual reverse-engineering effort may be relatively straightforward, and somewhat devoid of instances of large sections of code whose functions defy explanation without some serious time & effort and even some guesswork and trial-and-error. That would be nice for a change!
I'll continue with the reverse-engineering for the time being as there are a few aspects of the movement that I'm yet to decipher that would prevent a functional port at this point. But I'm still on a roll and would expect that it won't be too much longer before I can draw a line in the sand and start the implementation of the movement routines.
I'm also sorely tempted to break with tradition and start with the 6809 this time 'round. We'll see...
I've located the player movement (and dig) routines, which I eventually tracked down via the keyboard read call. I'm working through those now and have unearthed a few more crucial variables related to the player tracking throughout the play-field.
It's relatively early days yet, and I reserve the right to retract my statements, but I find some comfort in what I've found so far. Way back some 20+ years ago when I was attempting to write my TRS-80 version, I naively coded the player movement routines by handling each case explicitly in what equated to a rather lengthy case statement. It was classic spaghetti Z80 assembler, and whilst I knew it wasn't elegant, I was too eager to see results to attempt to formulate a more efficient algorithm.
On the surface, that appears to be pretty much how the original code was written. There's a ton of duplicated, cut 'n' paste code related to movement from what I've seen so far; perhaps not as bad as the code I first churned out but certainly nowhere near as elegant as the code that I finally crafted - albeit in its 3rd incarnation - on the PC. At least, it seems to be the case as far as I've gotten at this point.
It's also somewhat reassuring to note that the variables I've managed to identify thus far are heavily used throughout most of the codebase, in fact a surprisingly large percentage of the code seems to be concerned with manipulating these very variables, mostly related to movement (presumably of enemies and the player). If I'm right in assuming the same logic is used for both, then I guess there's little more to the actual game mechanics, other than digging (which should be relatively simple) and enemy AI.
What this suggest is, in stark contrast to a few other projects I've undertaken, that the actual reverse-engineering effort may be relatively straightforward, and somewhat devoid of instances of large sections of code whose functions defy explanation without some serious time & effort and even some guesswork and trial-and-error. That would be nice for a change!
I'll continue with the reverse-engineering for the time being as there are a few aspects of the movement that I'm yet to decipher that would prevent a functional port at this point. But I'm still on a roll and would expect that it won't be too much longer before I can draw a line in the sand and start the implementation of the movement routines.
I'm also sorely tempted to break with tradition and start with the 6809 this time 'round. We'll see...
Wednesday, 30 April 2014
Nothing to see here...
A severe lack of time and the arrival of new PCB's for my NGPACE project has seen very little progress on Lode Runner since my last post.
I have, however, spent a little time attempting to identify and isolate the player movement code but have only come to the conclusion that I really do need to invest more time on reverse-engineering the original code before I'm going to have much success on that front. So for now, those plans are on the back-burner until I have done exactly that.
What I may do in the mean-time, however, is take a look at porting the circular wipe function to the Coco3 (6809) at least. I'm still yet to implement the dual hires pages on either port, although on the Coco3 it should be quite straightforward, and I have already reserved the RAM for the 2nd page. At least it'll force me to do that much, and if it's not too much work, it'll look pretty cool!
Whilst my focus is primarily on the NGPACE project now that it's moving ahead, the beauty of this project is its suitability to working in small bursts that would otherwise be fruitless for work on the NGPACE designs. It's also easier to work on-the-go, and doesn't require the daily transport of hardware between home and the office.
I also feel a Neo Geo port is imminent... ;)
I have, however, spent a little time attempting to identify and isolate the player movement code but have only come to the conclusion that I really do need to invest more time on reverse-engineering the original code before I'm going to have much success on that front. So for now, those plans are on the back-burner until I have done exactly that.
What I may do in the mean-time, however, is take a look at porting the circular wipe function to the Coco3 (6809) at least. I'm still yet to implement the dual hires pages on either port, although on the Coco3 it should be quite straightforward, and I have already reserved the RAM for the 2nd page. At least it'll force me to do that much, and if it's not too much work, it'll look pretty cool!
Whilst my focus is primarily on the NGPACE project now that it's moving ahead, the beauty of this project is its suitability to working in small bursts that would otherwise be fruitless for work on the NGPACE designs. It's also easier to work on-the-go, and doesn't require the daily transport of hardware between home and the office.
I also feel a Neo Geo port is imminent... ;)
Saturday, 26 April 2014
Level 1 - 6809 style
The Coco3 (6809) port is now up-to-date with the Z80 version.
I must admit that this exercise is a real eye-opener for me; a die-hard Z80 fan for 35 years! I'm starting to really appreciate the 6809, in particular the indexed addressing modes and the instructions that support them. A few table-lookups that are down-right painful in Z80 require just a few lines of 6809... I'm afraid that I'm starting to prefer it over the Z80. Add the fact that the Coco3 graphics are bit-mapped and interleaved with CPU accesses, and I'm starting to enjoy the Coco3 port a lot more than the TRS-80 Model 4. And to think it was an after-thought!
Now to start on the code to move the player around the screen. I'm fairly certain that the same code is used to move the baddies as well, as I recall that they're essentially the same graphic, but striped to display as different colours to the player. So that should be a reasonable chunk of the game-play mechanics!
Incidentally, that would also bring it up to the point that I had my own version 'complete' on the TRS-80 Model 4 about 25-30 years ago!
The idea of adding a Neo Geo port is still nagging at me, especially since we just received some PCB's for my Neo Geo hardware project (see my other blog)! It's especially attractive because it would be almost a direct line-by-line translation of the 6502 (if I decided to go that way) plus all the rendering logic would be irrelevant! It would take me some time to set up all the tiles, but once that was done the code to bring it up-to-speed with the TRS-80 & Coco3 ports would be almost trivial!
![]() |
| Level 1 on the Coco3 |
I must admit that this exercise is a real eye-opener for me; a die-hard Z80 fan for 35 years! I'm starting to really appreciate the 6809, in particular the indexed addressing modes and the instructions that support them. A few table-lookups that are down-right painful in Z80 require just a few lines of 6809... I'm afraid that I'm starting to prefer it over the Z80. Add the fact that the Coco3 graphics are bit-mapped and interleaved with CPU accesses, and I'm starting to enjoy the Coco3 port a lot more than the TRS-80 Model 4. And to think it was an after-thought!
Now to start on the code to move the player around the screen. I'm fairly certain that the same code is used to move the baddies as well, as I recall that they're essentially the same graphic, but striped to display as different colours to the player. So that should be a reasonable chunk of the game-play mechanics!
Incidentally, that would also bring it up to the point that I had my own version 'complete' on the TRS-80 Model 4 about 25-30 years ago!
The idea of adding a Neo Geo port is still nagging at me, especially since we just received some PCB's for my Neo Geo hardware project (see my other blog)! It's especially attractive because it would be almost a direct line-by-line translation of the 6502 (if I decided to go that way) plus all the rendering logic would be irrelevant! It would take me some time to set up all the tiles, but once that was done the code to bring it up-to-speed with the TRS-80 & Coco3 ports would be almost trivial!
Wednesday, 23 April 2014
Level 1
I've re-thought my porting strategy slightly, and decided to 'emulate' the 6502 zero-page registers using the Z80 index register (specifically, IX). This allows a more congruent line-by-line port without juggling Z80 registers with the accumulator for zero-page access; the indexed addressing instructions work with most of the Z80 registers. The only caveat is the signed offset; if I'm careful that shouldn't be an issue.
For the 6809 I'll use the DP register for the same purpose.
I've defined some macro's in the Z80 code in case the whole IX thing doesn't work out, however it's looking pretty good so far. It allows me to use (Z80) A, B & C for (6502) A, X & Y respectively for zero-page operations with a 1:1 correspondence between the code bases.
After adjusting for the above I started work on coding the level display routine(s). I've hard-coded the level 1 data in the 'disk buffer' and coded the unpack and display routine. The latter was cut-down just to get something on the screen, and after fixing a few bugs in the pixel-doubling routine which didn't reveal themselves when displaying alphanumeric characters (due to their striped nature) I was greeted with this:
Between the convoluted 6502 code and the on-the-fly pixel-doubling, it's a bit slow but at least it looks the real deal! I'll stop at this point and bring the Coco3 (6809) code up-to-date.
For the 6809 I'll use the DP register for the same purpose.
I've defined some macro's in the Z80 code in case the whole IX thing doesn't work out, however it's looking pretty good so far. It allows me to use (Z80) A, B & C for (6502) A, X & Y respectively for zero-page operations with a 1:1 correspondence between the code bases.
After adjusting for the above I started work on coding the level display routine(s). I've hard-coded the level 1 data in the 'disk buffer' and coded the unpack and display routine. The latter was cut-down just to get something on the screen, and after fixing a few bugs in the pixel-doubling routine which didn't reveal themselves when displaying alphanumeric characters (due to their striped nature) I was greeted with this:
![]() |
| Level 1 - TRS-80 Model 4 |
Between the convoluted 6502 code and the on-the-fly pixel-doubling, it's a bit slow but at least it looks the real deal! I'll stop at this point and bring the Coco3 (6809) code up-to-date.
Tuesday, 22 April 2014
Double-buffered displays
I decided to press on with reverse-engineering the level display, with the intention of implementing it on the TRS-80 Model 4 (Z80), before bringing the Coco3 (6809) port up-to-date.
To this end, I have now commented the level display routine; from where it copies and then unpacks the data from the disk buffer, to the famous circular wipe as it renders the level to the display. I should note that I have identified, but not commented, the actual circular wipe routines (although I can tell you it draws a total of 170 concentric circles). I have also completely ignored the Apple II disk routines, as they are completely irrelevant to the port (and not very interesting either).
I did know that Lode Runner utilised both hires pages, and long suspected that this was integral to the circular wipe function. What I didn't know, but have now learned, is that the game itself uses both pages in a form of double-buffering. More specifically, it renders the level onto page 2, that gets copied to page 1 during the circular wipe, and then it subsequently wipes the players and enemies from page 2 so that only the background graphics remain. This scheme allows the 'sprites' to be rendered more efficiently during game-play, without the need to save background data on each sprite update.
As a result, there are actually a set of rendering routines; the first (used for the game status) simply over-writes the page data with the tile, a second which wipes said tile, and a third that OR's the tile onto the background. The first routine has a parameter that specifies which page is to be rendered onto, the latter work implicitly with both pages.
It's clear now that the TRS-80 and Coco3 ports will require a second screen buffer. Lode Runner does actually display from page 2 in some instances, but I believe that is restricted to the High Score screen and the level editor (which I may not bother porting), so hopefully I can work around that requirement. It's not an issue on the Coco3, but the TRS-80 will definitely not be able to display from the 2nd page.
I'm now coding the TRS-80 Model 4 level rendering routines. As part of the process I'll need to go back and implement the 2nd screen page; something I avoided up until now. Who knows, maybe I will decide to tackle the circular wipe, given that I'll need to copy the data between pages anyway.
To this end, I have now commented the level display routine; from where it copies and then unpacks the data from the disk buffer, to the famous circular wipe as it renders the level to the display. I should note that I have identified, but not commented, the actual circular wipe routines (although I can tell you it draws a total of 170 concentric circles). I have also completely ignored the Apple II disk routines, as they are completely irrelevant to the port (and not very interesting either).
I did know that Lode Runner utilised both hires pages, and long suspected that this was integral to the circular wipe function. What I didn't know, but have now learned, is that the game itself uses both pages in a form of double-buffering. More specifically, it renders the level onto page 2, that gets copied to page 1 during the circular wipe, and then it subsequently wipes the players and enemies from page 2 so that only the background graphics remain. This scheme allows the 'sprites' to be rendered more efficiently during game-play, without the need to save background data on each sprite update.
As a result, there are actually a set of rendering routines; the first (used for the game status) simply over-writes the page data with the tile, a second which wipes said tile, and a third that OR's the tile onto the background. The first routine has a parameter that specifies which page is to be rendered onto, the latter work implicitly with both pages.
It's clear now that the TRS-80 and Coco3 ports will require a second screen buffer. Lode Runner does actually display from page 2 in some instances, but I believe that is restricted to the High Score screen and the level editor (which I may not bother porting), so hopefully I can work around that requirement. It's not an issue on the Coco3, but the TRS-80 will definitely not be able to display from the 2nd page.
I'm now coding the TRS-80 Model 4 level rendering routines. As part of the process I'll need to go back and implement the 2nd screen page; something I avoided up until now. Who knows, maybe I will decide to tackle the circular wipe, given that I'll need to copy the data between pages anyway.
Monday, 21 April 2014
Score, Men, Level
With the tiles converted and message display routine implemented, the usual supporting routines for displaying various formats of integers was next. These were quite straightforward and offered little resistance to my reverse-engineering efforts. I worked my way through the 'Game Status' display quite quickly:
This exercise has, not surprisingly, revealed the memory locations for the score, number of men and level, which will come handy for later reverse-engineering exercises. Also, the display score routine actually adds a (4-digit) parameter value to the score before displaying, so that's one less routine that remains.
I now need to go back and optimise the character/tile data lookup; taking a leaf out of the Apple II version I'll need to create a couple of sets of look-up tables which will, unfortunately, add a few hundred more bytes to the tile data, but on average speed it up immensely.
The Z80 port currently supports both single-pixel and pixel-doubled display, with a simple .define. Because I'm pixel-doubling on-the-fly it is quite a bit slower than it could be. No doubt I'll ultimately need to optimise it to account for the limited graphics bandwidth on the uLabs Grafyx Solution board, but for now I prefer the flexibility that it offers. It provides a benchmark for performance, also serves as a template for the 6809 code, mitigates the need to support different tile data formats for now, and may even come in useful on another Z80 port one day!?! Regardless, all indications are that there are only a handful of routines that write to the display, so it's not a lot of code to optimise.
Once tile-lookup optimisation is done, I'll bring the 6809 version up-to-date and then go hunting for the level display routine(s). In the process I'll no doubt find the famous circular screen wipe, though I don't plan on implementing that until right at the end of the entire porting exercise.
![]() |
| The 'Game Status' display on the TRS-80 Model 4 (Z80) |
This exercise has, not surprisingly, revealed the memory locations for the score, number of men and level, which will come handy for later reverse-engineering exercises. Also, the display score routine actually adds a (4-digit) parameter value to the score before displaying, so that's one less routine that remains.
I now need to go back and optimise the character/tile data lookup; taking a leaf out of the Apple II version I'll need to create a couple of sets of look-up tables which will, unfortunately, add a few hundred more bytes to the tile data, but on average speed it up immensely.
The Z80 port currently supports both single-pixel and pixel-doubled display, with a simple .define. Because I'm pixel-doubling on-the-fly it is quite a bit slower than it could be. No doubt I'll ultimately need to optimise it to account for the limited graphics bandwidth on the uLabs Grafyx Solution board, but for now I prefer the flexibility that it offers. It provides a benchmark for performance, also serves as a template for the 6809 code, mitigates the need to support different tile data formats for now, and may even come in useful on another Z80 port one day!?! Regardless, all indications are that there are only a handful of routines that write to the display, so it's not a lot of code to optimise.
Once tile-lookup optimisation is done, I'll bring the 6809 version up-to-date and then go hunting for the level display routine(s). In the process I'll no doubt find the famous circular screen wipe, though I don't plan on implementing that until right at the end of the entire porting exercise.
Subscribe to:
Posts (Atom)


