Page 1 of 3

Daytona USA 2 High Scores

Posted: Thu Mar 27, 2025 10:10 am
by MO-120FF
Hi all,

a couple of friends and I are currently competing against each other in Daytona USA 2 (Battle On The Edge). Since it takes a while for the attract mode to display the high score list, I'm developing a small tool that reads the best times on the Daytona USA 2 beginner's track from the NV RAM file and displays them.

The initials are easy to handle but I struggle with the times. The actual times in the high score list are as follows:
1 - ZER - 2:17:71
2 - ZER - 2:17:99
3 - ZER - 2:18:05
4 - ZER - 2:18:10
5 - ZER - 2:18:13

I found this method to read the times from the NV RAM file:

1) Identified relevant hex values for each time:
Rank 1: 1EAA (Offset 22DE)
Rank 2: 1EBA (Offset 22FE)
Rank 3: 1EBD (Offset 231E)
Rank 4: 1EC0 (Offset 233E)
Rank 5: 1EC2 (Offset 235E)

2) Calculate the times for each rank. Here is the example for rank 1:
1EAA hex = 7850 dec (seems the value is in frames)
7850 + 72 (fixed value) = 7922 frames
7922 frames / 57.52416 (FPS of the game) = 137.716048 seconds
137.716048 seconds = 2 minutes, 17 seconds, 71.6 centiseconds
Matches the high score time of 2:17:71 (if truncated)

But the algorithm isn't 100% precise for all times. Depending on whether I round or truncate, there is a few ms difference for some times.

Actual / Rounded / Truncated
2:17:71 / 2:17:72 / 2:17:71
2:17:99 / 2:17:99 / 2:17:99
2:18:05 / 2:18:05 / 2:18:04
2:18:10 / 2:18:10 / 2:18:09
2:18:13 / 2:18:13 / 2:18:13

Does anyone know how exactly Daytona USA 2 stores time values in the NV RAM file and calculates them for display?

Re: Daytona USA 2 High Scores

Posted: Thu Mar 27, 2025 10:00 pm
by Bart
No idea but you could probably try to debug it by finding where the PowerPC reads these values from and tracing the code to see what it does. A couple of ideas and questions:

- How did you find this offset of 72?
- Are these being computed using floating point numbers or could the game code be using some fixed point representation?
- Is it timing dependent? That is, if you modify interrupt/frame timing in Model3.cpp, do the values displayed in the game suddenly change a bit? It's possible that the game is doing something like using the real-time clock to count frames elapsed in a given interval of real time rather than relying on the assumption that the frame rate is 57.52416 Hz.
- Can you supply raw values for each of those examples you gave?

Re: Daytona USA 2 High Scores

Posted: Fri Mar 28, 2025 1:55 pm
by MO-120FF
Thanks Bart for your reply — and for the great emulator you wrote. It’s giving us more fun than any modern game right now.

Going through Supermodel’s source or debugging the original PowerPC code was what I originally wanted to avoid, but it looks like I’ll have to go into it :)

Regarding your questions:

- How did you find this offset of 72?
Pure trial and error. We just kept trying until we found an algorithm that delivered the desired results — and the 72 frames could make sense in the context of gaming. BTW, "we" means myself with AI support (Claude was the only one that could help).

- Are these being computed using floating point numbers or could the game code be using some fixed point representation?
I assume the game uses floating point, but I tried both methods — floating point and integer — and got the same results.

- Is it timing dependent?
Frankly, I have no idea. It’s just a high score list stored in NV RAM — it shouldn't depend on anything. I’d use ASCII and integers to keep it as simple as possible. But who knows, that was Sega in the 90s ;)

- Can you supply raw values for each of those examples you gave?
The only raw values I use are the ones from the NV RAM dump — the 2-byte values. But there could be more values in the dump that I need to include in the calculation. That’s what I want to find out. The example calculation shows all the values I use to find the final algorithm.

I've attached the region of the dump I’m currently looking into (as a JPG to better see the ASCII values. Let me know if you’d prefer the data posted).

Again, thanks for your reply. Any further pointers would be super helpful.

Re: Daytona USA 2 High Scores

Posted: Sat Mar 29, 2025 5:40 am
by Bart
I found the following function in Daytona 2, which I suspect is related to this calculation:

Code: Select all

0x0006A47C: 0x38E00039	li	r7,0x00000039                                              
0x0006A480: 0x7CA23B96	divwu	r5,r2,r7          ; r5 = r2 / 57                         
0x0006A484: 0x1D050039	mulli	r8,r5,0x39        ; r8 = (r2 / 57) * 57                  
0x0006A488: 0x7CC81050	sub	r6,r2,r8            ; r6 = r2 - (r2/57)*57                 
0x0006A48C: 0x1CC603E8	mulli	r6,r6,0x3E8       ; r6 = (r2-(r2/57)*57) * 1000          
0x0006A490: 0x7CC63B96	divwu	r6,r6,r7          ; r6 = r6 / 57                         
0x0006A494: 0x38E0003C	li	r7,0x0000003C                                              
0x0006A498: 0x7C853B96	divwu	r4,r5,r7          ; r4 = (r2/57)/60                      
0x0006A49C: 0x1D04003C	mulli	r8,r4,0x3C        ; r8 = ((r2/57)/60) * 60               
0x0006A4A0: 0x7CA82850	sub	r5,r5,r8            ; r5 = (r2/57) - r8                    
0x0006A4A4: 0x7C643B96	divwu	r3,r4,r7          ; r3 = ((r2/57)/60) / 60               
0x0006A4A8: 0x1D03003C	mulli	r8,r3,0x3C        ; r8 = ( ((r2/57)/60) / 60 ) * 60      
0x0006A4AC: 0x7C882050	sub	r4,r4,r8            ; r4 = (r2/57)/60 - ((r2/57)/60) * 60  
0x0006A4B0: 0x4E800020	bclr	0x14,0                                                   
Everything is done with integer arithmetic. Note that 0x3c is 60, 0x39 is 57, and 0x3e8 is 1000. The number of frames is passed into r2. The code then appears to do the following (Python):

Code: Select all

total_seconds = num_frames // 57

num_frames_subsecond = num_frames - (total_seconds * 57)
milliseconds = (num_frames_subsecond * 1000) // 57

total_minutes = total_seconds // 60

seconds = total_seconds - total_minutes * 60

minutes = total_minutes - (total_minutes // 60) * 60

print(f"{minutes}:{seconds}:{milliseconds}")
But this still isn't quite correct:

0x1eaa = 2:17:719
0x1eba = 2:18:000
0x1ebd = 2:18:052
0x1ec0 = 2:18:105
0x1ec2 = 2:18:140

Not sure what's going on here.

Re: Daytona USA 2 High Scores

Posted: Sun Mar 30, 2025 1:15 pm
by MO-120FF
Thanks. Indeed, seems the algorithm only uses the general-purpose integer registers. I'll dig through it a bit and try more variations to find other potential models that deliver the desired results. I'll come back and update if I'm able to figure it out.

Re: Daytona USA 2 High Scores

Posted: Sun Mar 30, 2025 7:04 pm
by Bart
Note that the code was actually from dayto2pe not daytona2. I assume they are the same. It would be interesting to stop at this breakpoint in the debugger and try to understand how the values are consumed. I didn't check that. Nevertheless, if the calculation is performed here, I would expect these values to be translated directly into ASCII characters for display. But they clearly don't come out the way you show (unless you transcribed the displayed results incorrectly?)

Re: Daytona USA 2 High Scores

Posted: Sun Mar 30, 2025 8:28 pm
by Bart
Daytona 2 has a different algorithm but it produces the same results. Are you absolutely sure you transcribed the results correctly? I can't ever match the last one or the second one. Here are two routines in Daytona 2 that do the exact same thing, but the second one packs the results into a single 32-bit output comprising 4 parts.

Code: Select all

Function1:
0x0004CFF8: 0x38E00039	li	r7,0x00000039
0x0004CFFC: 0x7CA23B96	divwu	r5,r2,r7
0x0004D000: 0x1D050039	mulli	r8,r5,0x39
0x0004D004: 0x7CC81050	sub	r6,r2,r8
0x0004D008: 0x1CC60064	mulli	r6,r6,0x64
0x0004D00C: 0x7CC63B96	divwu	r6,r6,r7
0x0004D010: 0x38E0003C	li	r7,0x0000003C
0x0004D014: 0x7C853B96	divwu	r4,r5,r7
0x0004D018: 0x1D04003C	mulli	r8,r4,0x3C
0x0004D01C: 0x7CA82850	sub	r5,r5,r8
0x0004D020: 0x7C643B96	divwu	r3,r4,r7
0x0004D024: 0x1D03003C	mulli	r8,r3,0x3C
0x0004D028: 0x7C882050	sub	r4,r4,r8
0x0004D02C: 0x4E800020	bclr	0x14,0

Function2:
0x0004D030: 0x38800039	li	r4,0x00000039
0x0004D034: 0x7CA22396	divwu	r5,r2,r4
0x0004D038: 0x1CC50039	mulli	r6,r5,0x39
0x0004D03C: 0x7CC61050	sub	r6,r2,r6
0x0004D040: 0x1CC60064	mulli	r6,r6,0x64
0x0004D044: 0x7CC62396	divwu	r6,r6,r4
0x0004D048: 0x50C3063E	rlwimi	r3,r6,0,0x000000FF	; r3 = (r3 & 0xffffff00) | (r6 & 0xff)
0x0004D04C: 0x3880003C	li	r4,0x0000003C
0x0004D050: 0x7CC52396	divwu	r6,r5,r4
0x0004D054: 0x1C46003C	mulli	r2,r6,0x3C
0x0004D058: 0x7C422850	sub	r2,r5,r2
0x0004D05C: 0x5043442E	rlwimi	r3,r2,8,0x0000FF00	; r3 = (r3 & 0xffff00ff) | ((r2 << 8) & 0x0000ff00)
0x0004D060: 0x7CA62396	divwu	r5,r6,r4
0x0004D064: 0x1C45003C	mulli	r2,r5,0x3C
0x0004D068: 0x7C423050	sub	r2,r6,r2
0x0004D06C: 0x5043821E	rlwimi	r3,r2,16,0x00FF0000	; r3 = (r3 & 0xff00ffff) | ((r2 << 16) & 0x00ff0000)
0x0004D070: 0x50A3C00E	rlwimi	r3,r5,24,0xFF000000	; r3 = (r3 & 0x00ffffff) | ((r5 << 24) & 0xff000000)
0x0004D074: 0x4E800020	bclr	0x14,0
It's actually easier to understand Function 2 because it explicitly packages the outputs using RLWIMI instructions. The equivalent Python code is:

Code: Select all

num_frames = int(nvram_value)
total_seconds = num_frames // 57

num_frames_subsecond = num_frames - total_seconds * 57
centiseconds = (num_frames_subsecond * 100) // 57

total_minutes = (total_seconds // 60)
seconds = total_seconds - total_minutes * 60

total_hours = total_minutes // 60
minutes = total_minutes - total_hours * 60

print(f"{total_hours}h {minutes}:{seconds:02d}:{centiseconds:02d}")
Weirdly, I don't see that subroutine being called anywhere (unless it is somehow invoked dynamically and I'm just missing it). The first one is indeed used and I think it just uses the results in registers r4, r5, and r6 to print to the tilemap layer. I have no idea where some of the values you are observing came from.

Re: Daytona USA 2 High Scores

Posted: Mon Mar 31, 2025 9:08 am
by MO-120FF
Yep, double checked the scores again and they are correct.

The algorithm in D2 and D2PE could be different as PE shows milliseconds instead of centiseconds as high score.

Thanks for updating the code. Is the Python code the translated PowerPC assembler code, or does it represent an algorithm that runs in addition to the assembler code?

D2_HighScore2.jpg
D2_HighScore2.jpg (219.32 KiB) Viewed 143309 times

Re: Daytona USA 2 High Scores

Posted: Mon Mar 31, 2025 9:33 pm
by Bart
The Python code is a direct translation of the assembly language logic. I first wrote it directly, 1:1 for each op code, and then renamed the registers to variable names representing what is being calculated.

My guess is that this code is being run every frame to compute the lap time. It is possible that there is some other piece of code involved but I can't readily find it (I haven't tried using the Supermodel debugger or logging to identify what happens to the frame counts read from NVRAM). It would be very weird for the code to differ but anything is possible.

Re: Daytona USA 2 High Scores

Posted: Wed Apr 02, 2025 1:18 pm
by MO-120FF
By chance, I just drove another 2:17.71 time. I used this opportunity to compare the relevant parts of the two hex dumps and marked the bytes that have changed in yellow.

It looks like there are 12 bytes somehow connected to the times. Between rank 1 and rank 2, only the 1E AA word is the same. It seems the actual time is really only coded in these two bytes. I'll take these findings and try to dig deeper into these 12 bytes.