Thursday, September 10, 2009

Debugging

I reached a point where it was time to switch to a competent interactive debugger, instead of working out of a series of text files that were becoming increasingly difficult to manage.

It took me a good few days to transfer my comments and knowledge to the new system. But the payoff is massive - I've immediately spotted mistakes, and identifying blocks of code has become a lot less painful.



In the screenshot you can see the commented block of code that transfers the in-game timer to the on-screen HUD.

Yesterday I also documented how the main branch table works in memory. In fact, I'm not even sure if branch table is the correct terminology. Figuring this out was important because actually determining the program flow in Outrun is tricky. The list of functions that are finally called is generated at runtime, and differs at different points of the game.

Firstly, there's a portion of ROM that contains a series of long addresses. Each of these addresses is a pointer to another portion of ROM. This second portion of ROM contains a set of properties related to the function, and the final address of the function itself. The final step is to compile these to a table within RAM. This table contains bits so that the functions can be enabled or disabled.

I presume this is some kind of optimisation, but it seems to add a lot of indirection and makes following the flow of code rather tough from the onset. Maybe some level of obfuscation was the idea? Perhaps someone reading who is a 68000 wiz will know.

Update: Figured this out. Some useful information in the comments regarding this from one of the blog readers and myself :)

3 comments:

Anonymous said...

Firstly, there's a portion of ROM that contains a series of long addresses. Each of these addresses is a pointer to another portion of ROM. This second portion of ROM contains a set of properties related to the function, and the final address of the function itself. The final step is to compile these to a table within RAM. This table contains bits so that the functions can be enabled or disabled.

I presume this is some kind of optimisation, but it seems to add a lot of indirection and makes following the flow of code rather tough from the onset. Maybe some level of obfuscation was the idea? Perhaps someone reading who is a 68000 wiz will know.

This is a method of 68000 programming that I once used, basically is allows you to represent 0 and 1s (or whatever) as a switching method to turn on and off routines. I would use it in very large productions.


It's ideal when you're coding and wish to isolate just portions for say testing, also saves assembling time.

I'd have to dig deep back through my old source code (17years) to find an example.

It does seem somewhat obfuscate from a debuggers point of view and it's not designed as a form of optimisation in terms of cpu cycles, nor to mislead you, its just simply a method of breaking code into segments.

Paul.

yt said...

Hi Paul,

Thanks for the info.

As I've debugged the code in more detail, I've realised exactly what you say is true.

Furthermore, the way sprites are handled in the game benefits from this technique. A sprite can be spawned mid-game, (whether it's traffic on the road, or some element of the scenery) and the table updated with a specific routine and properties to handle the sprite in question.

I'll probably uncover more benefits to this approach as I progress.

Chris :)

Anonymous said...

It is actually a very efficient method of programming because you modularise everything, it also demonstates the programmer is very proficient in there mastery of 68000. By conditioning your coding techniques in this manner allows it to also be interchangeable with other productions.

#Example of an entire production using Moduled Code.


#START:

BSR Save OS State Code #mandatory
BSR Setup Environment Code #mandatory

#IRQ:
BSR Interrupt Handlers IRQs #mandatory

#Toggled Routines:

BSR Sprite Handler Code = YES/NO (0/1)
BSR Music Player Code = YES/NO (0/1)
BSR Scroller Text Code = YES/NO (0/1)
BSR Text Printer Code = YES/NO (0/1)

#OS Restore:
BSR Restore OS State Code #mandatory

rts


YES/NO Flag rs.b 0

INCBIN: (Include binaries) Hardware Equates (incbin..)


#Offsets addr$ to Mandatory Routines:

Save OS State Code rs.l /(incbin..)
Setup Environment Code rs.l /(incbin..)
Interrupt Handlers IRQs rs.l /(incbin..)
Restore OS State Code rs.l /(incbin..)

#Offsets addr$ to Toggle Routines:

Sprite Handler Code rs.l /(incbin..)
Music Player Code rs.l /incbin..)
Scroller Text Code rs.l /incbin..)
Text Printer Code rs.l /incbin..)

This is from memory as a very simple example.