So there you are, playing a pass-the-controller team game of Mario Party 3, expecting the usual shenanigans, the back and forth of stars and coins, and a tight race to the end. Wrong. This mini-game in the first few turns of the game, a bit of luck, and Team Luigi has their coins multiplied by 32, and suddenly it’s a race for second place.
Here’s how it plays out: you land on the Shy Guy tile, all your coins are anted, and you’re given a choice to bet either on the Big Chomp finishing his cake first to double your bet, or on the Little Chomp doing so first for 4/8/16/32/64x your bet. Team Luigi chooses Little Chomp for a 32x multiplier, wins, and brings their 21-coin count up to a ridiculous 672. The game is a wash after that and they finish with 9 stars, with 2nd place sitting at 3 or 4.
But what are the actual odds? I sought to find out, and using Project64’s debugger, I did.
Plan of Attack
First, I tried determining where the math was done that actually multiplied your coin amount, since that would probably use a different value based on whether you won or lost the bet. So I saved state before starting the mini-game, and noticed that loading state would often have the game start with a different seed, and thus, a different multiplier for the Little Chomp bet. Searching for that value in memory, I found that the Big Chomp multiplier gets stored at 0x8010CF60 and the Little Chomp multiplier at 0x8010CF70. As evidence, here’s me manipulating those memory addresses to get Big Chomp to 64x and Little Chomp to 4x.
This lead was kind of a dead end, since the values written here were used long after the actual winner got decided. If I traced where they came from, it probably would’ve got me to the answer sooner, but I instead chose to look at nearby memory regions and see if I could find where the player’s selection was written. I found a region of memory that stored a 0 or a 1 based on your bet, and was later read to compare with which Chomp won.
Following accesses to that memory, I traced where the “winner” was written, and eventually tracked it down to this two-iteration loop, starting at address 0x80107194:
loop_start: sra v0,v0,0xf addu v0,v0,a3 lh v0,0x0(v0)=>FUN_8010cfe4 bne v0,a2,LAB_b4311170 ; Checks v0 != 6 _addiu v0,a0,0x1 lui at,0x8011 sh zero,-0x300e(at)=>LAB_8010cff0+2 lui at,0x8011 sh zero,-0x300a(at)=>FUN_8010cff4+2 lui at,0x8011 sh a0,-0x3094(at)=>LAB_8010cf6c lui at,0x8011 sh a1,-0x3012(at)=>LAB_8010cfec+2 sw v1,0x14(s0) move a0,v0 sll v0,v0,0x10 sra v0,v0,0x10 slti v0,v0,0x2 bne v0,zero,loop_start
Of note is the line with the comment noting that V0 is compared with A2, containing the value 6 (hard-coded in the assembly). As a hunch, I assumed this was checking whether either Chomp had finished the cake, and sure enough, forcing V0 to a value of 6 on the first iteration immediately caused Big Chomp to win and Little Chomp to win if done on the second iteration. Here’s proof of me betting on Big Chomp and forcing either winner before any cake has been eaten:
Great! So now to figure out where those counters get incremented and why. Long story short, this block here starting at 0x80310314:
; Gets random integer from 0-9 inclusive mtc1 v0,f12 ; Stores 10.0 in F12 nop jal RandomIntegerUpToF12 _cvt.s.W f12,f12 ; Sets V1 to 5 if 4x, 6 if 8x, 7 if 16x, etc. lui v1,0x8011 lh v1,-0x3000(v1)=>multiplier_threshold ; If our random 0-9 >= V1, Little Chomp eats! slt v0,v0,v1 beq v0,zero,small_wins: _nop ; Otherwise, generate another number from 0-9 lui at,0x4120 ; This is hexadecimal representation of 10.0 mtc1 at,f12 jal RandomIntegerUpToF12 _nop ; If our random 0-9 >= 6 (40% chance), eat two slti v0,v0,0x6 beq v0,zero,add_two_big _nop ; Otherwise, eat one and jump to the store lui v1,0x8011 addiu v1,v1,-0x301c lhu v0,0x0(v1)=>big_chomp_counter(0x8010cfe4) j store_big; Jump has one-instruction delay _addiu v0,v0,0x1 add_two_big: lui v1,0x8011 addiu v1,v1,-0x301c lhu v0,0x0(v1)=>big_chomp_counter(0x8010cfe4) addiu v0,v0,0x2 store_big: sh v0,0x0(v1)=>big_chomp_counter(0x8010cfe4) ; ... returns before the next block ... ; Same 40% deal to eat two for Little Chomp... small_wins: lui at,0x4120 mtc1 at,f12 jal RandomIntegerUpToF12 _nop slti v0,v0,0x6 beq v0,zero,add_two_little: _nop lui v1,0x8011 addiu v1,v1,-0x301a lhu v0,0x0(v1)=>little_chomp_counter(0x8010cfe6) j store_little _addiu v0,v0,0x1 add_two_little: lui v1,0x8011 addiu v1,v1,-0x301a lhu v0,0x0(v1)=>little_chomp_counter(0x8010cfe6) addiu v0,v0,0x2 store_little: sh v0,0x0(v1)=>little_chomp_counter(0x8010cfe6)
TLDR? This block is periodically run based on a counter incremented every frame to, at fixed intervals, decide which Chomp gets a bite, and whether that Chomp gets one or two. It generates an integer from 0-9 and compares it with a dynamic value that’s set based on the multiplier (which is randomly selected as you load into the mini-game). If it’s less than that value, Big Chomp gets a bite, if it’s greater than or equal to it, Little Chomp does. Then, whichever Chomp wins the current bite rolls another 40% chance to get two bites in.
So now the only mystery remaining is: where are the random number thresholds that we compare our 0-9 roll to stored? Following our initial lead for where the multiplier gets read from, we find there are two parallel tables. The multiplier table starting at 0x8010CC78:
0x0004 ; 4x
0x0008 ; 8x
0x0010 ; 16x
0x0020 ; 32x
0x0040 ; 64x
And immediately following it, the threshold table for whether the Big Chomp gets the bite at 0x8010CC86:
0x0005 ; 0-9 < 5 for 4x = 50%
0x0006 ; 0-9 < 6 for 8x = 60%
0x0007 ; 0-9 < 7 for 16x = 70%
0x0008 ; 0-9 < 8 for 32x = 80%
0x0009 ; 0-9 < 9 for 64x = 90%
Just to demonstrate that, lets add a 44x multiplier with a guaranteed win for the little guy (that is, we’ll write 0x002C to all entries in the first table and 0x0000 to all entries for the second table)…
As a fun experiment, I wrote this code snippet to simulate this behaviour for 5 million iterations, and produced the following results:
For multiplier 4X, little one wins 49.9582% For multiplier 8X, little one wins 29.0432% For multiplier 16X, little one wins 12.7799% For multiplier 32X, little one wins 3.5343% For multiplier 64X, little one wins 0.34936%
Well then, 3.5% chance of ruining the party for everyone else. Interesting to note that you should always bet on Little Chomp with a 4x multiplier, and also, the Chomp dialogue of them saying how hungry they are doesn’t actually tell you anything (or at least no more than just looking at the multiplier)!
In case you’re curious, the multipliers themselves are equally likely as well, calculated starting at 0x80106064:
; Gets random index from 1-5 lui at, 0x40a0 ; 5.0 in floating point mtc1 at, f12 jal RandomIntegerUpToF12 addui v0, v0, 0x0001 addu s1, v0, zero ; Continuing at 0x801060FC (S1 is yet changed) ; Load from threshold table above sll v0, s1, 16 sra v0, v0, 15 addu v0, sp, v0 lhu v0, 0x0020(v0) lui at, 0x8011 sh v0,-0x3000(at)=>multiplier_threshold