On Accuracy, Health, and Bond Bonus'
4 years ago
United States

I just went through Austrian Castle, finishing with full health and 100% Accuracy, in order to get a better understanding as to how the points are tallied, here's how it works:

https://imgur.com/a/NZZbpIB

Looking at this we see that the maximum bonus points you can get for any stage are 100k. 25k each for the Bond bonus, Accuracy, Health, and being under the Par time (no problem for us, right?). With 200k for Dispatched/Subdued Enemies.

Bond Bonus: Grabbing the bond bonus in each stage gets you an automatic 25000 points.

Dispatched/Subdued: Max points for this category is 200k, but the exact point value for each enemy is going to vary greatly depending on the stage.

Accuracy: This needs some more testing, but it is directly related to how many shots you take vs how many hits you have, with a max point value of 25000. This is going to be where most of the leeway is in running this game, as the better people get at running it, the better their overall accuracy will be, thus removing the need to kill quite as many dudes to move on.

Health: So the max health is 9hp, if you finish the stage with full health you get the full 25000 bonus, each point you lose takes another 11.1111%. So finishing the stage with 7 health will give you a final percentage of 77% and a point value of 19250. Making sure you finish with top health makes a huge difference in a speedrun situation, but since the enemy accuracy is an absolute crapshoot, there's not too much you can do to make sure you finish up with a ton of health.

Time: This is a free 25k to us speedrunners.

Does someone want to go through and find out the point values of the enemies in each stage?

United States

I posted a message about this in the OSC Discord, but I'll reiterate and expand upon it here for easier access.

The game essentially calculates a percentage value (from 0% to 100%, other than time) for each of the five categories, then multiplies each by 250 to get a score value, other than Dispatched, which is multiplied by 2000 (and time, which works differently). The percentage (other than the Bond bonus) is calculated by dividing two numbers to get a ratio, multiplying by 100, and rounding down. This can be described as the function Percent(Num, Den) = floor(Num * 100 / Den). There are some quirks with how the division is calculated, which I'll mention at the end, but for any case you're likely to run into, this is how it works.

The Bond bonus percentage is the simplest. If ANY emblem is collected, then this is 100%. Otherwise, it's 0%. If there's any possibility of collecting more than one emblem in a level, then there is no benefit to collecting more than one. The number of emblems collected is stored as a 16-bit value at 0x0200073C.

The Dispatched \ Subdued percentage is calculated by keeping a tally of both the number of enemies loaded and enemies taken out, and calculating Percent(Enemies_Disp, Enemies_Total). These counts are stored as 16-bit values, with the dispatched count at 0x02000740 and the total count at 0x02000742. The total count appears to be updated with each map load, so in theory it might be possible to influence this number if a significant sequence break is found. However, for the time being, we can treat this as a constant value for each level. I'll add another message here once I get totals for each mission.

The Accuracy percentage is calculated by keeping a tally of both the number of shots the player has made and how many shots hit something important, and calculating Percent(Shots_Hit, Shots_Fired). It doesn't appear that they have to hit an enemy specifically, as shooting the window in the first mission still counts as a hit. It is not clear at this point how the game counts shots that hit multiple enemies (if possible), but there is a check to make sure the percentage doesn't go above 100%. These counts are stored as 32-bit values, with the hits count at 0x02000734 and the shots count at 0x02000730.

The Health percentage is calculated by taking the number of health chunks still lit up (from 0 to 8 ), and calculating Percent(Health + 1, 9). This only takes into account the health that you complete the level with, so collecting a full armor right at the end of the level is enough to get full points for this category. The health value is stored as a 16-bit value at 0x0200074A.

Time is more complicated to compute, and is also rather strange because the score is not linear with respect to the time taken. The game takes the time taken, rounded down to the nearest whole second, and the par time for the mission, and calculates Percent(Par_Time, Time_Taken). Note that having the actual time taken as the denominator means that the score has an inverse relationship to the time score. From here, the game multiplies the percentage by 1000, and then checks the resulting value X. If X ≥ 125,000, then the time score is set to 25,000. If X < 50,000, then the time score is set to -100,000. Otherwise (if 50,000 ≤ X < 125,000), the time score is X - 100,000. This has the effect that taking exactly twice the par time results in a time score of -50,000, but taking exactly one second longer results in a time score of -100,000. I suspect that this was a mistake in the code, due to the -100,000 matching the subtracted value in the middle case. If we assume that it was meant to be -50,000 instead, then we can rewrite this equation as being 1000 * clamp(Percent(Par_Time, Time_Taken) - 100, -50, 25). The time taken is stored as a 32-bit value of the count of vertical interupts (i.e. 1/60th seconds) at 0x02000738. The level's par time in seconds is stored as a 16-bit value at 0x02000748, which in turn comes from an array of 16-bit values starting at 0x0801EE74 (stored in mission order). Here's a desmos graph of the resulting time score using the first mission's par time: https://www.desmos.com/calculator/jyi4vyumug

The final score is taken by adding together all five component scores, resulting in a score between -100,000 and 300,000 inclusive. For all of the missions, Bronze requires 190,000 points, Silver requires 245,000 points, and Gold requires 270,000 points. This is pretty much all there is to the scoring system.

Now, here's some information about how the game performs division. The GBA's CPU lacks any instructions to perform division, so it has to use some tricks to make it work. Starting at 0x08021400, there is an array of 16,384 32-bit values, where the X indexed value (starting at X=0) is equal to 2^32 / X (The exceptions being at X=0, X=1, and X=2, which are all equal to 2^31 - 1). Using this array I'll call DivTable, the game performs division by calculating Div(Num, Den) = floor((Num * DivTable[Den]) / 2^32) (with the floor of the division by a power of two in these equations being implemented using a bit shift). There are a few special cases to this, though. The first special case is that it short circuits if Den < 2, which means that Div(Num, 0) = Div(Num, 1) = Num. This has some interesting effects, most notably that never firing a weapon counts as 0% Accuracy. The next special case is when Den = 2, where it simply performs a bit shift to perform the division. This is because the value for DivTable[2] isn't accurate (nor is there a valid signed value it could use). The last case is when DivTable[Den] doesn't exist, which happens when Den ≥ 16,384. When this happens, the game approximates the value using Div(Num, Den) = Div(floor(Num / 2), floor(Den / 2)). This could produce some interesting rounding issues if it ever comes up, but it would have to be a pretty exceptional case for it to come up at all.

EDIT: Also, using Hard difficulty appears to add a flat 10,000 points to your score. The bonus is added before the ranking (bronze, silver, gold) is determined, so you need slightly less for bronze on hard mode.

EDIT 2: Coming back to this game, and spending more time reverse-engineering this, I realized there's a type of edge case that should also be mentioned. You see, DivTable[X] isn't quite 2^32 / X, but instead is floor(2^32 / X). This adds an additional rounding step to the division which can result in incorrect values in some edge cases. While these edge cases are unlikely to come up in practice, it's worth keeping in mind that the percentage that the game shows can be rounded down by more than a whole percent in a worst-case scenario.

編集者 投稿者 10 months ago
TwoFoxSix これを好き
United States

So I went through all the levels with an emulator and looked at the game's total enemies count during the score screen. There's a decent chance that I made a mistake somewhere here, but this should all be accurate.

Mission 1: 31 Enemies Mission 2: 35 Enemies Mission 3: 27 Enemies Mission 4: 33 Enemies Mission 5: 21 Enemies Mission 6: 52 Enemies Mission 7: 70 Enemies Mission 8: 71 Enemies Mission 9: 9 Enemies

Just to note, what counts as an enemy seems to be kind of weird. The helicopter at the end of the first mission, as well as every boss I checked, do count as an enemy. However, the infinitely respawning enemies at the end of Mission 4 do NOT count.

While I was at it, I also collected all the par times for each mission. These are visible in-game, so this isn't new information, but I figured I'd aggregate it here for future reference.

Mission 1: 300 Seconds Mission 2: 360 Seconds Mission 3: 510 Seconds Mission 4: 540 Seconds Mission 5: 300 Seconds Mission 6: 630 Seconds Mission 7: 690 Seconds Mission 8: 960 Seconds Mission 9: 180 Seconds

EDIT: This was all done on Normal difficulty, though my basic testing suggests this shouldn't change for Hard difficulty.

編集者 投稿者 2 years ago
venditta そして TwoFoxSix これを好き
ゲームスタッツ
フォロワー
21
走行
47
プレイヤー
9
最近の実行
レベル: Mayhew's Estate
レベル: Austrian Castle
モデレーター