Showing posts with label rewrite. Show all posts
Showing posts with label rewrite. Show all posts

Wednesday, October 31, 2012

Sounding Off

I'm currently implementing sound in Cannonball (the name for the cross platform OutRun engine). Much like my approach to the video hardware, the sound hardware will be emulated, whilst the actual Z80 program code is converted to readable C++.

I've implemented the SDL sound layer to output the audio, hooked up emulation of the Sega PCM chip and created the interface between the main program code and sound code. I've successfully converted enough of the Z80 code to trigger basic PCM samples. The entire Z80 rom is decompiled and commented, so progress from here should be steady.

There will (eventually) be two approaches to audio in Cannonball. The first approach will purely use the original ROMs for music and sound as discussed above. The second approach will allow players to configure audio files as replacement music tracks. This will allow you to play the game with the various remixes that have been produced over the years.

Once sound is implemented, I'll open up the source code repository to public access. This will allow everyone to play with and port the source code to new platforms for non-commercial purposes.

Saturday, October 20, 2012

Z80 Program Code - Part 2

Let's take a look at the structured format of sound information. Here's a relatively straightforward example demonstrating the setup of the Checkpoint PCM sample.

; Voice 1, Checkpoint
ROM:6F91 data_voice1:    dw data_voice1_c   ; Offset to channel setup below

The sample is played through two channels simultaneously, presumably to boost its volume. 

; Voice 1, Channel Setup
ROM:6F99 data_voice1_c:  db 2               ; Number of channels
ROM:6F9A                 dw data_voice1_c1  ; Address of Channel 1 Setup
ROM:6F9C                 dw data_voice1_c2  ; Address of Channel 2 Setup

This block represents the default setup for the 32 byte block mentioned in the previous post. It's not actually 32 bytes, but the remainder of the space is padding to zero by the program code. I've cut the second entry short in the interests of space as it's very similar.

; Voice 1, Checkpoint (PCM Samples: Channel 1) - Default 0x20 area setup
ROM:6F9E data_voice1_c1: db 80h             ; Flags: Enable
ROM:6F9F                 db 1000110b        ; Flags: Mute & Channel Index
ROM:6FA0                 db 2               ; End Marker
ROM:6FA1                 dw 0
ROM:6FA3                 dw 1
ROM:6FA5                 dw data_voice1_c1p; Address of commands
ROM:6FA7                 db 0
ROM:6FA8                 db 20h            ; Offset for positioning information
ROM:6FA9                 db 0
ROM:6FAA                 db 0
ROM:6FAB                 db 0

; Voice 1, Checkpoint (PCM Samples: Channel 2)
ROM:6FAC data_voice1_c2: db 80h
; Snip: Similar to the above block
ROM:6FB9                 db 0

Here's where things gets a little interesting; what follows is a series of commands that correspond to a particular z80 routine, along with their arguments.

; Voice 1, Checkpoint (PCM Properties)
ROM:6FBA data_voice1_c1p:db 93             ; 93 = Command: PCM Set Pitch
ROM:6FBB                 db 48h            ;    value = pitch
ROM:6FBC                 db 82h            ; 82 = Command: PCM Sample Volumes
ROM:6FBD                 db 17h            ;    value = left channel vol
ROM:6FBE                 db 2Eh            ;    value = right channel vol
ROM:6FBF                 db 0DCh           ; DC = Command: Sample Index
ROM:6FC0                 db 28h            ;    value = checkpoint
ROM:6FC1                 db 99h            ; 99 = Command: PCM Finalize

ROM:6FC2 data_voice1_c2p:db 93h            ; 93 = Command: PCM Set Pitch
ROM:6FC3                 db 48h            ;    value = pitch
ROM:6FC4                 db 82h            ; 82 = Command: PCM Sample Volumes
ROM:6FC5                 db 2Eh            ;    value = left channel vol
ROM:6FC6                 db 17h            ;    value = right channel vol
ROM:6FC7                 db 0DCh           ; DC = Command: Sample Index
ROM:6FC8                 db 28h            ;    value = checkpoint
ROM:6FC9                 db 99h            ; 99 = Command: PCM Finalize

These commands index a table of routines which is as follows. Not all of these routines are used, as I imagine this area of the code is used across other Sega titles. I've highlighted in red the entries used by the above sample.

ROM:0B93 BigRoutineTable:
ROM:0B93    dw YM_Dec_Pos           ; YM: Decrement Position In Sequence (80)
ROM:0B95    dw YM_SetEndMarker      ; YM: Set End Marker. 
ROM:0B97    dw PCM_SetVol           ; PCM: Set Volume (Left & Right Channels) (82)
ROM:0B99    dw YM_Dec_Pos           ; YM: Decrement Position In Sequence (80)
ROM:0B9B    dw YM_Finalize          ; YM: End (84)
ROM:0B9D    dw YM_SetNoise          ; YM: Enable Noise Channel (85)
ROM:0B9F    dw loc_409              ; Unused?
ROM:0BA1    dw YM_SetModTab         ; YM: Enable/Disable Modulation table
ROM:0BA3    dw WriteSeqAddr
ROM:0BA5    dw SetSeqAddr           ; Set Next Sequence Address
ROM:0BA7    dw YM_GetLoopAdr        ; de = new YM loop address
ROM:0BA9    dw YM_SetNoteOffset     ; YM: Set Note/Octave Offset (8B)
ROM:0BAB    dw YM_DoLoop            ; YM: Loop Sequence Of Commands (8C)
ROM:0BAD    dw loc_46B              ; Unused?
ROM:0BAF    dw loc_471              ; Unused?
ROM:0BB1    dw YM_Enable_Correspnd  ; YM: (Unused) Enable corresponding channel (8F)
ROM:0BB3    dw YM_Disable_Correspnd ; YM: (Unused) Disable corresponding channel (90)
ROM:0BB5    dw YM_SetBlock          ; YM: Set Block - Called First When Setting Up (91)
ROM:0BB7    dw YM_DisableNoise      ; YM: Disable Noise Channel (92)
ROM:0BB9    dw PCM_SetPitch         ; PCM: Set Pitch (93)
ROM:0BBB    dw YM_MarkerData        ; FM: End Marker - Do not calculate, use value from data (94)
ROM:0BBD    dw YM_MarkerHigh        ; FM: End Marker - Set High Byte From Data (95)
ROM:0BBF    dw YM_ConnectRight      ; FM: Connect Channel to Right Speaker (96)
ROM:0BC1    dw YM_ConnectLeft       ; FM: Connect Channel to Left Speaker (97)
ROM:0BC3    dw YM_ConnectCentre     ; FM: Connect Channel to Both Speaker (98)
ROM:0BC5    dw PCM_Finalize         ; Write Commands to PCM Channel (99)

In case you were wondering the 0xDC command which is not shown in the table above triggers a separate piece of code, which also triggers drum samples and so forth in the music tracks. Anyway,  let's take a look at a simple routine - setting the pitch of a PCM sample.

ROM:03C4 PCM_SetPitch:
ROM:03C4   bit     6, (ix+1)       ; If channel is muted, don't set pitch
ROM:03C8   jp      nz, set_pitch
ROM:03CB   ld      a, a
ROM:03CC   ret
ROM:03CD set_pitch:
ROM:03CD   ld      a, (de)         ; a = New pitch (read from setup table in rom)
ROM:03CE   ld      (ix+16h), a     ; Set relevant area in 32 byte block that controls pitch
ROM:03D1   ret

The music tracks work in a similar way, but with a much longer and more complex series of commands.

Originally, I thought the Z80 might be used in a 'dumb' manner and simply stream preformatted audio data to the various chips. But its usage is much more sophisticated as demonstrated above.

Thursday, October 18, 2012

Z80 Program Code - Part 1

I've almost finished decompiling OutRun's Z80 program code, so I'll be providing high level information regarding its workings over a series of posts.

The Z80 processor controls two pieces of sound hardware; a custom Sega PCM controller and a Yamaha YM2151 FM sound chip. This was a fairly standard configuration for Sega boardsets at the time. As you'd expect, some of the Z80 program code is in fact shared with other games of the era. However, most of the code is unique and written solely with OutRun in mind.

Commands are sent to the Z80 from the master 68000 program code. The Z80's interrupt routine reads from port 0x40 and places the values received into a sequential set of locations in RAM. Commands are high level and consist of a byte corresponding to a Z80 routine. So sending 0x81 plays the 'Passing Breeze' music, whereas 0x9d triggers the 'Checkpoint' PCM sample. From the 68000's point of view, playing a sound is simple and the complexity is nicely masked.

In addition to these commands, the 68000 sends data relating to the volume and pitch of the Ferrari's engine tone. It also sends volume and panning information relating to the passing traffic. This ensures that when you drive past a vehicle, the volume of its engine is proportional to the y distance from your Ferrari and the stereo panning corresponds to the x difference.

The core loop to achieve everything is as follows:

ROM:0039 main_loop:
ROM:0039   call    DoFMTimerA      ; Wait for timer on YM2151 chip 
ROM:003C   call    ProcessCommand  ; Process Command sent by 68000
ROM:003F   call    ProcessChannels ; Run logic on individual sound channel (both YM & PCM channels)
ROM:0042   call    ProcessEngines  ; Ferrari Engine Tone & Traffic Noise
ROM:0045   call    ProcessTraffic  ; Traffic Volume, Panning, Pitch
ROM:0048   jp      main_loop

The Z80 maps the 16 channels of the PCM chip to various uses. 6 are reserved for the music's drum samples, 4 are used for sampled sound effects and the remainder are used for the Ferrari's engine sound and passing traffic. Each channel is allocated a 32 byte area of RAM by the Z80 program code, which stores its current state. This concept is extended to include the channels from the YM chip which are also allocated to these areas of RAM.

The usage of the 32 byte area of RAM differs dependent on whether it represents a YM or PCM channel. The area contains everything from basics including volume and pitch for PCM samples through to complex YM configuration including positional information within the current block of audio commands, section loop counters and the address of the next data block. This 32 byte block is used as a starting point to configure the separate PCM RAM area, which has a different format and to program the YM's registers.

Next time, I'll explain the interpreted language stored within the Z80 code. This is used by the Z80 to program the sound hardware. And you'll see how the music and sound effects are actually stored as an interpreted sequence of commands that call functions within the code.

Monday, August 27, 2012

OutRun C++ Engine Tech Demo 2

At last, here's a new demo of the OutRun engine. The entire game is ported, aside from sound and the service mode.

In terms of functionality, this release is bare bones. There is no menu system yet, all options are hardcoded and you have to play the game windowed.

It would be great if you could report bugs (and I'm sure there will be plenty) in the comments below. Please verify any subtleties against MAME for reference.


Stats:

  • Conversion time from decompiled code: 1 year 10 months.
  • Estimated ratio of time spent coding vs. debugging: 1:5
  • Road rendering code: 1500 lines
  • Ferrari handling & rendering: 1680 lines
  • Code to render level objects: 1050 lines
  • General sprite handling code: 890 lines
  • Traffic handling code: 675 lines
  • Code to handle crash routines: 1450 lines

Keys:

  • Cursors: Steering
  • Z: Accelerate
  • X: Brake
  • Space: Gear Change
  • 5: Insert Coin
  • 1: Start
  • F1: Pause
  • F2: Advance a frame when paused (useful for observing visual problems)
  • F3: Toggle/Freeze timers. (i.e. infinite time)
In a future revision, there will be options to custom the controls and the analogue sensitivity. I find MAME's default setup too twitchy, so you'll find the steering a little more heavy in comparison. 


Enhancements over original:

In a future revision, enhancements will be optional and there will be a menu toggle to enable/disable them.


Requirements:


Download:

Thursday, August 02, 2012

Light at the end of the tunnel...

It's been nearly two years since I started rewriting OutRun, and three years since I begun decompilation work. This is, and feels like, a long time. According to Yu Suzuki, the original game took four programmers between eight and ten months to complete, so I'll take some reconciliation from the fact this was only a part-time project.

Following the success of the decompilation work, I expected the rewrite to be plain sailing. In fact, the rewrite proved tough - really tough! The size and complexity of the codebase meant I spent an inordinate length of time debugging. Writing your own code from scratch is comparitively easy; your intentions are clear and tracking errors is straight forward. Finding the source of a bug in thousands of lines of ported assembler can be a nightmare.

Debugging ultimately became a case of stepping through the suspected area of code line-by-line and comparing results with the MAME debugger. I invoked crazy tactics along the way; I coded routines to utilise MAME memory dumps for the road layer to quickly determine whether bugs were caused by erroneous code or if data in memory was at fault. This also enabled me to compare the original with my port from an identical starting point.

The other complication was the way in which the original codebase was designed and structured. The style of code varies dependent on who was working on it and by god, they produced a lot of it. The hardware specifications were insane by 1986 standards, and the coding team appear to have approached the project with the view that space and clarity were not a primary concern. There is a huge amount of code duplication and multiple routines that perform similar tasks with minor modifications. Despite evidence of code reuse at Sega, there should be no doubt that this is disposable code, not a reusable game engine. In fairness, the programming team would have been under considerable time pressure.

So what's next? I'm going to port the final chunk of code to handle the game completion sequence. This consists of a big switch table to manually send commands to the sub CPU handling the road layer (similar to the road split, but not quite the same), code to control the Ferrari AI during this period (similar to attract mode, but not quite the same), code to blit the timing information to the screen (similar to other digit blitting routines but not quite the same) and code to handle the animation sequences (similar to the start line intro sequence but not quite the same). Now, you're beginning to understand the OutRun codebase!

Once this is complete, I will release a new build for testing purposes. I was going to release sooner, but I'm so close in terms of porting the entire core engine I'm going to hold back. This build will run at 60fps and feature a selection of other minor improvements not in the original game. From this point onwards, the fun begins and we can start to include extra functionality and enhancements. It will also be a good point to port the code to a variety of platforms. I will be looking for help once I tidy up the codebase a little further.

I hope that explains where the project is, feel free to comment below if you have questions.

UPDATE: Tantalisingly close... bonus points code done, bonus sequence AI done, bonus track control done. Just the animation sequences now.


Monday, July 23, 2012

Odds & Ends

Nothing interesting to report. Since returning from holiday I've converted the high-score entry code and course map screen. When I used to develop games professionally, I didn't enjoy working on these elements and I can't say the feeling has changed much!



Next I'll move onto some of the remaining attract mode code. Once I've tied up some loose ends, I'll release a playable tech demo in order to gather bug reports. And that will be a little more interesting. 

Update:
The core game engine is now ported, apart from the end sequences, test mode and sound code. We're getting close...



Thursday, June 14, 2012

60 FPS

My ported OutRun engine now runs at 60 FPS, as opposed to the standard 30 FPS of the original arcade machine. It's beautiful and smooth. Although you can easily switch back to 30 FPS if desired.

The game was intended to run at 60 FPS originally, but reduced to 30 FPS for performance reasons. You can tell this by studying at the game code. The vertical interrupt code, including the routine to increment the timer, is intended to be called 60 times a second. Whereas the game engine is intended to be ticked 30 times a second.

The Sega Saturn version is the only other version to support 60 FPS mode. However, I can probably increase the frame rate further still... let's see!

Aside from that, I ported the the HUD and related logic (including the 'Extend Time' code between stages). The intro sequence with the Ferrari driving in and flag waving is also complete.

I will release a new tech demo in around 6 weeks so you can give it a spin and report any bugs.

Update: Tonight I recoded the engine to run at a ridiculous 120 FPS. Then I realised my LCD monitor doesn't even support this refresh rate - doh! The code becomes a little more hacky to support such a frame-rate causing a few minor bugs to surface. For this reason, I probably won't support 120 FPS. Still - it was a fun experiment, sort of...

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. 


Tuesday, January 31, 2012

Gateway's Broken Arches

Have you noticed that Gateway's arches are randomly broken in the original OutRun? At times arches don't join, sometimes they float in the air and occasionally complete pillars are missing. 

It's not very noticeable at high speed, and the precise nature of the breakage isn't consistent. Overall though, it spoils the illusion of what would otherwise be a cool level. 


For my rewrite, there's a simple solution to this problem; but not for the original game sadly. OutRun's software engine can display 76 scenery sprites at any one time, which are initialized dynamically as the level progresses. Further sprites are reserved for traffic and other essential objects. 

Each Gateway arch comprises 4 sprites (two pillars and two joining sections). So we can display 19 complete arches at any one time. Therefore, on complex stretches of road where no free slots can be allocated, some of the pillar components are simply skipped.

Thankfully, we don't have memory or speed restrictions on a modern PC and can allocate additional slots to dynamically spawn sprites. In fact, it's as easy as changing a single number. And here's a screenshot to (somewhat) prove it. 


The illusion when moving through the level is greatly improved, and when I eventually increase the frame rate beyond the original 30fps, this level will be awesome! 

Sunday, January 29, 2012

OutRun C++ Engine Tech Demo

Finally, after years of hard labour, here's a technical demo of the OutRun C++ port. The demo showcases recent work porting the core level rendering engine. The benefit of rewriting the engine, is that it will facilitate modifications and enhancements to the original game that Yu Suzuki only dreamed of.

Now, let's get arty and check out some stills from the demo. I've implemented the ability to change the horizon y coordinate, so we can experience viewpoints never seen in the original game. How about a bird's eye view of the start line?


We can also straddle left and right, so it's possible to find further interesting camera angles. Although this one reveals that our surf-boarding friend isn't actually in the water! Messing with the original engine can highlight its limitations of course. This demo allows you to scroll further left and right than the original engine, which can cause glitches. 


It's fun to be able to browse the scenery in detail: 


Here's a beach-side postcard scene for you:


And finally a view down the final straight of Coconut Beach before the road fork. Alas, the road fork code hasn't been ported yet, so this is the road to nowhere at the moment. 


The keys for the demo are:
  • Space: Toggle automatic movement through level
  • Cursor Up: Advance slowly
  • Cursor Left/Right: Move camera left/right
  • A/Z: Adjust horizon
  • Escape: Quit
Requirements:
Other Notes:
  • Having control over the rendering engine surfaces glitches and limitations present in the original code. Using unpatched roms, the sprite zoom bug mentioned in this post is evident. You can use patched roms to eliminate this. 
  • There is a bug where a random shadow pops into view dependent on the camera x coordinate. This is present in MAME as well, but is hard to reproduce when you're actually racing through the level. I need to get this verified on hardware to help track down a solution and determine whether it's a video emulation issue or a bug in the original codebase. 
Download here: outrun_tech_demo1.zip

Let me know what you think by leaving a comment below.

Wednesday, November 23, 2011

OutRun Conversion Update

I haven't written about the OutRun conversion to C++ for some time. That's because I put it on hold whilst I focused on the anniversary edition, amongst other projects. Having a break was sensible and necessary, as it's such intense work.

Compared with other 68k conversions I've worked on professionally, this is much tougher, due to the sheer complexity and in some cases poor quality of the original code. Even after a first draft, a huge amount of refactoring will be needed so that the codebase can be extended.


Things are slowly coming together. Don't hold your breath whilst waiting for this; I'm doing it at a very steady pace. I've taken a screenshot of my desktop, which shows that much of the level data is now being parsed. Underlying this, many of the sprite rendering routines are ported. I've included a MAME screenshot for comparison so you can see what's currently missing. 

Sunday, December 05, 2010

Road Layer and Slave CPU Code Converted

The entire code for the slave 68k CPU has been ported. This CPU solely controls road generation and interfacing with the road hardware. It's probably the most complex area of the game code. As expected, debugging the code was relatively painful. The code now needs a considerable clean-up, but I'll do that once more of the game code is hooked up, to ensure it's more obvious if I break something whilst refactoring.

The following screenshot shows a section of curved track using both road layers on Coconut Beach. You can begin to see that all the elements are coming together and we're now in a position where we have the building blocks to rewrite the higher level code.

Wednesday, November 24, 2010

Translation Update & Driving Cabinet

Currently going gang busters on the slave CPU road code. It's big, it's ugly, but it's unfortunately necessary to translate a large chunk to C++ before I can proceed with more visible aspects of the game code. The level generation is highly dependent on it. Even after translation, I expected to spend a couple of weeks doing a line by line debug - Visual Studio vs. Mame Debugger. Let battle commence.

Meanwhile, Garnet Hertz provided an update back in October, with regard to their real life OutRun driving cabinet.

Check out a recent video here:

Tuesday, November 16, 2010

Sprite Support Implemented

A big step forward; I now have full sprite support in my framework.

Furthermore, I have ported all the low-level OutRun routines from 68k to C++ that abstract the sprite hardware from the general game code. This was a considerable effort and required some serious debugging.

You can think of the dependencies as follows:

High-Level OutRun Game Code (68k) -> Low-Level OutRun Sprite Routines (68k) -> Video Hardware.

I'm at the stage where the second two components in this sequence are done. The ported routines control some of the following areas:

  • Initializing and caching sprite palette data in RAM
  • Ordering sprites based on priority
  • Converting the programmer friendly format used by OutRun game objects to the format required by hardware
  • Setting horizontal and vertical zoom settings from a lookup table
  • Setting the height and width from a lookup table in relation to the above
  • Setting the sprite anchor point
  • Setting rendering hints based on horizontal flip bits etc.

Here's a slightly dull screenshot, which shows the OutRun logo being rendered. Well most of it, the observant among you will notice I didn't hook up the bird sprites as it was getting late:


It doesn't look like much, but the important thing is I can initialize a sprite simply by setting a few jump table properties using fully ported code. Here's an example of the code required to initialize a sprite object, where 'e' is a jump table entry:

e->jump_index = 0;
e->x = 0;
e->y = 0x70;
e->road_priority = 0xFF;
e->priority = 0x1FA;
e->zoom = 0x7F;
e->pal_src = 0x99;
e->draw_props = 0;
e->control = 0;
e->shadow = 3;
e->addr = ADDRESS_OF_SPRITE_DATA;
map_palette(e);
do_spr_order_shadows(e);


So progress is good. Once I get to the stage where there is something more interesting, I'll release a demo build.

Saturday, November 06, 2010

Support for Tile Layers Implemented

The hardware tile layer is now supported in my framework. So in addition to the text layer previously mentioned, the ported code can now utilize tiles.

Here's a screenshot to provide an example of this, using ported code to display the tiles from the music selection screen:


Much of the detail from the music select screen is missing, because it also makes use of sprites, which are currently unsupported by the framework.

To summarize the components of the port, the following 68k code has been ported to C++ in order to reach the above stage:
  • Routines to setup palette ram
  • A new text routine to blit text with a height of two tiles to the text layer (this displays the Select Music By Steering text string) 
  • The routine which decompresses a tile map from rom and outputs it to tile ram
  • The routine to update tile hardware on a vertical interrupt
And the framework itself emulates the following:
  • Tile Layers
  • Text Layers
  • Palette Hardware
So we're getting to a stage where basic routines are coming along nicely. The final ported C++ code is more readable and far more concise than the original assembler.