Saturday, May 26, 2012

OutRun Inspired Commercial

Check out this OutRun inspired commercial for the Sonic drive-in restaurant chain in the US.


Monday, May 21, 2012

Verification

I implemented the AI code to handle automatically driving the Ferrari in Attract Mode. This means I've been able to test the accuracy of the ported engine in a number of ways, which is where my time has been spent recently.

The AI code works by reading the upcoming road layout and traffic data. It outputs appropriate acceleration, brake and steering values which are read by the standard game logic. I've been able to compare my ported game engine against the emulated game engine to ensure key values are identical.

I made both MAME and my port spew out a selection of values relating to the AI, car road position, car x coordinate, car speed and so forth. By comparing the outputs, I discovered and fixed subtle bugs that weren't noticeable by playing the game.

Unfortunately, about 330 frames into attract mode the values diverged. The difference was subtle. The culprit was the Ferrari handling logic reading different x coordinate values from the road data between versions. Assuming the road rendering code was flawed, I debugged the precise position in both versions by manually setting the distance into the level and executing the routine to generate the screen x coordinates from the raw data. Both routines output identical values, so that wasn't where the problem resided.

After much debugging and some head scratching, I realised the subtle difference was due to the CPU interleaving. For my port, I run the main CPU for a full tick, run the road CPU for a full tick and then call the vertical interrupt code. However, on the original game both CPUs run in parallel. (MAME simulates this with some rough CPU interleaving). This means the road x values read by the main CPU, which are previously generated by the road CPU can be subtlely different. The difference depends on the precise number of cycles used by the code path of the road CPU.

There is no point in simulating this behaviour as it would massively complicate the codebase. It makes no visual difference, as the AI logic still behaves identically and the Ferrari crashes at the same points in each stage. I would suspect that the hardware differs from MAME again as a result of this but I haven't verified this theory. (Whether the difference is noticeable to the naked eye is another matter). The Saturn port, perfect in many respects, also differs in this area as a result of the timing differences.

Verifying this may have been overkill, but accuracy is important to me. In this case, the difference is thankfully inconsequential. I'm now moving on to a couple of other bug fixes I've identified, which will be tricky to track down!

Tuesday, May 08, 2012

Crash Bang Wallop!

All collision and crash routines are ported.




So where are we with the project? Well, amazingly, the entire core game engine is now ported and fully working! Wooo! (It's amazing to me, when I think back to starting this impossible task a few years ago). 

There's still plenty to do before the initial port can be considered done, even if the remainder is somewhat easier. Let's take a look at what's coming next in no particular order:
  • Bonus sequence code. (This is going to be easy, but quite boring to port unfortunately)
  • Start sequence code. (Ferrari driving in from side, countdown lights and man waving flag)
  • Attract Mode AI
  • High Score Screen
  • Course Map Screen
  • Game Logic (Timers, HUD, Switching between various game modes)
  • Music Selection Screen (Somewhat started)
  • Attract Mode (Some areas started)
  • Service Mode (For completeness!)
  • Sound
And then we can move onto the optional enhancements, and the features that make this project really exciting. 

Sunday, April 08, 2012

Traffic Code Ported

Porting Update:
The traffic code is now ported and the end result is 600 lines of C++ and what appears to be the correct behaviour.

One aspect that the 80s home conversions missed, is that OutRun's traffic is relatively smart. If you drive up slowly behind it, it will accelerate to stay out of your way. It will also attempt to change lanes where possible to avoid the player and other AI traffic. Each traffic object has a series of flags to denote its proximity to other road objects.


The traffic logic works by grabbing the traffic objects from the ordered sprite display list. This has the advantage of meaning that the traffic is already z ordered correctly as an optimisation for proximity checks. The display list references the original objects and the logic code then goes to work on these. 

Next I'll be working on the ferrari crash routines. You may remember that I posted about them here a couple of years ago. Yes, it's been that long!

More bugs?
I know this attention to detail is becoming increasingly pedantic but today's random bug in the original game relates to the positioning of the passengers in the car. Come to a complete stop. Gently accelerate and you'll notice both man and woman shift to the right by a pixel. Slow down and they will shift left again. 

I've found the line in the original code that's causing this, but I'm not sure if this is a bug or intentional behaviour? I'm pretty sure the programmers got a check for the horizontal flipping of the Ferrari inverted when setting the passenger offsets. What do you think?

Scoring
It's been a while since I've written something non-technical. So let's explain OutRun's scoring logic:
  • You will score when both Ferrari wheels are on road. The amount you score depends on your speed. This value will be incremented 30 times a second.

    The table of values is as follows. The player's speed is used as an index into this table:
    0, 10, 20, 30, 40, 50, 60, 80, 110, 150, 200, 260, 330, 410, 500, 600, 710, 830, 960

    To score the highest value (960) you must be travelling at 287 kph.
  • Overtaking a car: 20,000 points
  • 100,000 points per 0.1 second of bonus time on game completion

Saturday, March 31, 2012

Mame graphics bug? Update: Solved!

Now is the following a bug in the original game, or MAME's video emulation?

In the below screenshot you can see a large shadow bottom left. What is it? Where has it come from? Who knows! 

Correct behaviour (car x position 0x1E3)

Shadow error bottom left (car x position 0x1E2)

  • You can reproduce this by manually setting the car x position to 0x1E2 in the mame debugger (offset 0x260050 in memory). 
  • Drive forward from the start line, and you'll see this shadow flicker on and off.
  • Setting the car position to 0x1E1 or 0x1E3 sees the shadow disappear as below.
Can someone try reproducing this on hardware? You probably want to give it a go on MAME first to get the hang of where to drive without using the debugger!

I suspect this is an issue with MAME's video emulation code, which I've used as a basis for my port, but would be nice to get verification.

Incidentally, I can't reproduce this issue on the Sega Saturn port. (Interestingly, the Saturn port also fixes some of the other bugs I found that can be reproduced on hardware). 

Update:
Thanks to Magic Knight for reproducing this on hardware. Surprisingly, it's a bug in the original game code. It only took 5 minutes to find and fix in my C++ translation. Here's the offending code:

// Hide Sprite if off screen
if (sprite_y2 < 256 || sprite_y > 479 ||
    sprite_x + width < 192 || sprite_x > 512)
{
    hide_hwsprite(input, output);
    return;
}

The fix is to change the highlighted > symbol to >=. Easy! I'm going to wait before patching the original game, just in case this has caused any side effects. 

The bug is caused by the shadow on the right hand side, wrapping to the left as it goes off-screen. 

Wednesday, March 21, 2012

Vroom Vroom

Some good news - the main Ferrari is implemented. Controls are also complete, which means you can drive through all the levels of the game, albeit without collisions or crash sequences yet. The handling feels right though. Here are some screenshots:

 

The code, as usual, is rather comical. The original assembler manages to combine sound effect triggering logic, score updates and logic to trigger tyre smoke when turning into sharp bends into one almighty routine. I guess coding practices have come on somewhat since 1986. It's kind of funny to be the first person to delve into this code in over 25 years. 

I continue to find bugs in the original codebase, which I'm optionally fixing in my conversion. There will probably be a menu option to toggle my fixes. The latest can be reproduced as follows:
  • Drive into the level a bit.
  • Come to a standstill.
  • Turn the wheel leftmost, then let it centre itself
  • Accelerate and notice the car veers off to the left, even though the wheel is now centered
I can patch this bug on the anniversary edition the next time I do a release, if there's demand. I suspect no-one ever noticed it though. Can it be reproduced easily on hardware?

Update: Yes, it's been reproduced on hardware. Interestingly, it can't be reproduced on the Sega Saturn port.

Saturday, March 10, 2012

Splitting Roads

At last, the core level engine that handles rendering of levels can be deemed complete:
  • Every stage renders correctly and the corresponding level object routines are complete.
  • The code to handle the road split is complete.
  • The code to transition tilemaps between levels is complete.
  • The code to fade the road and sky palettes is complete.
  • You can move through each level and seamlessly load the next one.
Being OutRun, there was an unbelievably large amount of code to handle the level transitions. For example, the road split works via a giant 15-way switch statement that looks at your position and sends commands to the sub CPU to alter the road. 

So now it's onto gameplay. I've been coding the routines to render and control the Ferrari. This higher level code is far easier to work with than the core level engine code. Debugging an error in this area of code now thankfully  involves a few minutes investigation, as opposed to the days some of the core rendering bugs took.  

The code is atrociously messy in places. For example, the logic to handle smoke under the Ferrari's tyres seems to have been inserted pretty much everywhere: in the road splitting code, gear changing code, level object rendering code etc. There is nothing modular about this game! 

To compensate for this, I allow my ported C++ classes to all access each other. They still have private members of course, but there's a global public reference to the class itself. The code could potentially be refactored at a later stage, but for now the focus is getting it ported and working.